[讨论] 《深入理解Java虚拟机-JVM高级特性与最佳实践》一段实例代码的疑问
xm_king
2011-10-27
package classloader; import java.io.IOException; import java.io.InputStream; public class ClassLoadTest { /** * @param args * @throws ClassNotFoundException * @throws IllegalAccessException * @throws InstantiationException */ public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException { ClassLoader myLoader = new ClassLoader() { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; System.out.println(getClass().getClassLoader()+":"+name); InputStream ins = getClass().getResourceAsStream(fileName); if (ins == null) { return super.loadClass(name); } byte[] b = new byte[ins.available()]; ins.read(b); return defineClass(name,b, 0, b.length); } catch (IOException e) { e.printStackTrace(); } return super.loadClass(name); } }; Object obj = myLoader.loadClass("classloader.ClassLoadTest").newInstance(); System.out.println(obj.getClass()); System.out.println(obj instanceof classloader.ClassLoadTest); } } 最近阅读了一下《深入理解Java虚拟机-JVM高级特性与最佳实践》第七章讲到类加载器的时候,有上面的代码。我在原来的代码中插入了 System.out.println(getClass().getClassLoader()+":"+name); 结果打印如下: sun.misc.Launcher$AppClassLoader@19821f:classloader.ClassLoadTest sun.misc.Launcher$AppClassLoader@19821f:java.lang.Object sun.misc.Launcher$AppClassLoader@19821f:java.lang.ClassLoader sun.misc.Launcher$AppClassLoader@19821f:classloader.ClassLoadTest$1 class classloader.ClassLoadTest false 第一句,第二句我明白了,第三句和第四句弄不清楚是为什么。 按照我的理解,java.lang.ClassLoader已经在方法的第一行就加载了。内部类classloader.ClassLoadTest$1也应该加载了的。有没有人给解释一下,是什么原因啊? |
|
xm_king
2011-10-27
第一行是正常的,因为我们调用自定义类加载器加载classloader.ClassLoadTest
sun.misc.Launcher$AppClassLoader@19821f:classloader.ClassLoadTest 第二行我也可以理解,我们自己加载的classloader.ClassLoadTest的父类Object需要加载 sun.misc.Launcher$AppClassLoader@19821f:java.lang.Object 但是,第三行和第四行是怎么出来的,难道是之前这两类没有加载吗?应该不是吧,这个确实让我比较费解。 sun.misc.Launcher$AppClassLoader@19821f:java.lang.ClassLoader sun.misc.Launcher$AppClassLoader@19821f:classloader.ClassLoadTest$1 我猜测一下,我们需要加载的classloader.ClassLoadTest引用到了java.lang.ClassLoader、classloader.ClassLoadTest$1以及Object,所以会调用当前的类加载器去加载? |
|
IcyFenix
2011-10-28
xm_king 写道 但是,第三行和第四行是怎么出来的,难道是之前这两类没有加载吗?应该不是吧,这个确实让我比较费解。 sun.misc.Launcher$AppClassLoader@19821f:java.lang.ClassLoader sun.misc.Launcher$AppClassLoader@19821f:classloader.ClassLoadTest$1 我应召来了= =# 先复习一个基础知识: JVMS JavaSE 7 写道 At runtime, a class or interface is determined not by its name alone, but by a pair: its binary name (§4.2.1) and its defining class loader.
运行时,一个“类”还由它的定义加载器所确定,只要定义加载器不一样,即使两个类来自同一个Class文件,在VM中也还是两个独立的类。 第四行中,虽然类(AppClassLoader:classloader.ClassLoadTest$1)是已经被加载了。但是这次加载的是(classloader.ClassLoadTest$1:classloader.ClassLoadTest$1)。 第三行中,java.lang.ClassLoader是委派给父类加载器了,没有走到defineClass去,你不妨把print语句挪到defineClass的前面看看。 另外,我建议你加进去的那句print,改为“System.out.println(getClass()+":"+name);”,这样可能更好理解一些。 |
|
xm_king
2011-10-28
IcyFenix 写道 xm_king 写道 但是,第三行和第四行是怎么出来的,难道是之前这两类没有加载吗?应该不是吧,这个确实让我比较费解。 sun.misc.Launcher$AppClassLoader@19821f:java.lang.ClassLoader sun.misc.Launcher$AppClassLoader@19821f:classloader.ClassLoadTest$1 我应召来了= =# 先复习一个基础知识: JVMS JavaSE 7 写道 At runtime, a class or interface is determined not by its name alone, but by a pair: its binary name (§4.2.1) and its defining class loader.
运行时,一个“类”还由它的定义加载器所确定,只要定义加载器不一样,即使两个类来自同一个Class文件,在VM中也还是两个独立的类。 第四行中,虽然类(AppClassLoader:classloader.ClassLoadTest$1)是已经被加载了。但是这次加载的是(classloader.ClassLoadTest$1:classloader.ClassLoadTest$1)。 第三行中,java.lang.ClassLoader是委派给父类加载器了,没有走到defineClass去,你不妨把print语句挪到defineClass的前面看看。 另外,我建议你加进去的那句print,改为“System.out.println(getClass()+":"+name);”,这样可能更好理解一些。 我理解你的意思,我的疑问在于,为什么会去加载java.lang.ClassLoader、classloader.ClassLoaderTest$1,也就是为什么loadClass()方法被调用了四次。 |
|
IcyFenix
2011-10-28
xm_king 写道 我理解你的意思,我的疑问在于,为什么会去加载java.lang.ClassLoader、classloader.ClassLoaderTest$1,也就是为什么loadClass()方法被调用了四次。 classloader.ClassLoaderTest$1的加载是因为newInstance()方法中为了创建classloader.ClassLoadTest的实例,查找它的构造器而导致。而java.lang.ClassLoader是因为是classloader.ClassLoaderTest$1的父类。 你可以尝试去掉newInstance()方法,这样就不会有后面2次加载。 |
|
xm_king
2011-10-28
IcyFenix 写道 xm_king 写道 我理解你的意思,我的疑问在于,为什么会去加载java.lang.ClassLoader、classloader.ClassLoaderTest$1,也就是为什么loadClass()方法被调用了四次。 classloader.ClassLoaderTest$1的加载是因为newInstance()方法中为了创建classloader.ClassLoadTest的实例,查找它的构造器而导致。而java.lang.ClassLoader是因为是classloader.ClassLoaderTest$1的父类。 你可以尝试去掉newInstance()方法,这样就不会有后面2次加载。 非常感谢你的热心答复,但是我又有了新的问题了,呵呵。 classloader.ClassLoaderTest$1是classloader.ClassLoadTest的匿名内部类,那为什么创建classloader.ClassLoadTest的实例查找构造器的时候,会去加载它的匿名内部类classloader.ClassLoaderTest$1呢?难道在classloader.ClassLoadTest的构造器里面有classloader.ClassLoaderTest$的引用吗?但是反编译class文件之后,并没有发现啊。 烦请你再次解释一下,谢谢。 |
|
IcyFenix
2011-10-29
xm_king 写道 非常感谢你的热心答复,但是我又有了新的问题了,呵呵。 classloader.ClassLoaderTest$1是classloader.ClassLoadTest的匿名内部类,那为什么创建classloader.ClassLoadTest的实例查找构造器的时候,会去加载它的匿名内部类classloader.ClassLoaderTest$1呢?难道在classloader.ClassLoadTest的构造器里面有classloader.ClassLoaderTest$的引用吗?但是反编译class文件之后,并没有发现啊。 烦请你再次解释一下,谢谢。 由于没有强制规定,JVM何时加载类是由JVM实现自行决定的,只要在类初始化之前已经加载了就可以了,这个和<init>中是否用到要加载的类没有必然联系——用到了当然要加载,没有用到也不代表不会加载。 所以要看classloader.ClassLoaderTest$1为何加载,只能实际跟踪代码了。 从java代码一侧来看,调用路径是 引用 java.lang.Class.newInstance()->getDeclaredConstructors()->getDeclaredConstructors0()->ClassLoaderTest$1.loadClass()
那关键就在于最后一个环节getDeclaredConstructors0()->loadClass()是怎么来的,getDeclaredConstructors0()是一个native方法,它的实际代码在hotspot\src\share\vm\prims\jvm.cpp#JVM_GetClassDeclaredConstructors中。 跟进去之后,发现类加载请求来自于: // Ensure class is linked k->link_class(CHECK_NULL);link_class定义在hotspot\src\share\vm\oops\instanceKlass.cpp中,link_class()这个函数看名字就知道它是干啥的了,它的实际工作在link_class_impl()里面完成,继续跟进去发现请求来自于: bool verify_ok = verify_code(this_oop, throw_verifyerror, THREAD);我测试用的是比较土的6u23,不过编出来只要是1.6的Class文件,就还是走类型检查验证,请求来自于SpiltVerifier验证器: ClassVerifier split_verifier( klass, message_buffer, message_buffer_len, THREAD); split_verifier.verify_class(THREAD);(PS:我测试过关了SpiltVerifier,确定类型推导验证也会导致加载这个类)。 跟到这里就可以解释了,原因是JVM在校验main()方法的字节码时导致的类加载。 |
|
xm_king
2011-10-29
引用 确定类型推导验证也会导致加载这个类
非常感谢IcyFenix! |