欢迎大家来到IT世界,在知识的湖畔探索吧!
之前在JVM类加载机制(一)中介绍过类加载机制的基本原理,今天从代码角度来展示下从磁盘上加载一个类字节码的效果。
准备一个简单的类,目标是从磁盘上加载这个类的字节码,然后调用test()方法:
public class HelloWorld {
private static int i = 3;
static {
System.out.println("init");
}
public static void main(String[] args) {
System.out.println("Hello World");
}
public void test(){
System.out.println("test");
}
}
欢迎大家来到IT世界,在知识的湖畔探索吧!
通过下面的命令将HelloWorld.java编译成HelloWorld.class:
欢迎大家来到IT世界,在知识的湖畔探索吧!javac HelloWorld.java
# output
HelloWorld.class
编译好的HelloWorld.class其实就是一个二进制的字节码文件。
可以从javap命令来进行反编译:
javap HelloWorld.class
欢迎大家来到IT世界,在知识的湖畔探索吧!Compiled from "HelloWorld.java"
public class HelloWorld {
public HelloWorld();
public static void main(java.lang.String[]);
public void test();
static {};
}
也可以通过sublime软件打开,如下:
我们从磁盘上加载这个字节码文件,首先读取字节流:
public static byte[] readClassByte(String filePath) throws IOException {
FileInputStream inputStream = new FileInputStream(filePath);
long fileSize = new File(filePath).length();
byte[] allBytes = new byte[(int)fileSize];
inputStream.read(allBytes);
return allBytes;
}
这个方法参数filePath就是HelloWorld.class文件的路径,读取的字节流存放在byte[]类型的数组中,然后通过来defineClass方法加载字节码:
byte[] classBytes = readClassByte("/Users/pro/Desktop/Helloworld.class");
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Method method = Class.forName("java.lang.ClassLoader")
.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
method.setAccessible(true);
Class clazz = (Class) method.invoke(classLoader, classBytes, 0, classBytes.length);
由于defineClass方法是protected类型的,因此除子类之外的其他外部类无法直接调用这个方法,所以这里采用反射的方式来调用它,主要是为了展示类加载的效果,最后返回clazz对象。
这里的这个clazz对象实际上就是HelloWorld类,然后下一步就是调用HelloWorld类中的方法,继续使用反射:
Method method = clazz.getDeclaredMethod("test");
method.invoke(clazz.newInstance());
最后就可以看到输出的结果:
init
test
可以看到除了输出test结果之外,还有init,这个是来自于HelloWorld类中静态代码块的执行,如下:
static {
System.out.println("init");
}
根据类加载的上一篇JVM类加载机制(一)可以看到上面的过程主要模拟了一个类加载、链接、初始化的过程,而这个init就是在初始化的时候输出的。
这里的类加载过程比较明显,但是链接过程被隐藏了,如果要显示展示链接过程,可以加上如下的代码:
Method method = Class.forName("java.lang.ClassLoader")
.getDeclaredMethod("resolveClass", Class.class);
method.setAccessible(true);
method.invoke(classLoader,clazz);
这里的resolveClass方法就是用来对类做链接的,与defineClass方法一样,都是protected类型的,所以也通过反射调用。
完整的代码如下:
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class CustomLoadClass {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException, InstantiationException {
//类加载
byte[] classBytes = readClassByte("<二进制类文件的路径>");
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Method method = Class.forName("java.lang.ClassLoader")
.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
method.setAccessible(true);
Class clazz = (Class) method.invoke(classLoader, classBytes, 0, classBytes.length);
//类链接
method = Class.forName("java.lang.ClassLoader")
.getDeclaredMethod("resolveClass", Class.class);
method.setAccessible(true);
method.invoke(classLoader,clazz);
//类调用(包含初始化)
method = clazz.getDeclaredMethod("test");
method.invoke(clazz.newInstance());
}
public static byte[] readClassByte(String filePath) throws IOException {
FileInputStream inputStream = new FileInputStream(filePath);
long fileSize = new File(filePath).length();
byte[] allBytes = new byte[(int) fileSize];
inputStream.read(allBytes);
return allBytes;
}
}
以上主要通过模拟一个二进制类加载、链接到执行的过程来理解类加载概念上的原理,如果要在一个产品中去自定义加载一些类或者jar包,主要是通过自定义类加载器的方式来做,下一篇JVM类加载文章会进行介绍。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/17707.html