今台看了一点关于类加载器的东西,总结一下算作一个小复习吧,尽可能的每天学习一点知识点,积水成渊吧
首先我们如何看当一个类运行的时候相关的加载信息呢 在命令行下可以采用java -verbose:class 类名称 ,如果是在eclipse下(本人其实用的是myeclipse) 设置一下运行参数就可以。如下图
这样你就可以在运行的时候就可以看到相关类是如何加载的
我们运行一下
你会看到类似如下的信息,只是类似并不是雷同。
[Opened D:\Program Files\Genuitec\Common\binary\com.sun.java.jdk.win32.x86_1.6.0.013\jre\lib\rt.jar]
[Loaded java.lang.Object from D:\Program Files\Genuitec\Common\binary\com.sun.java.jdk.win32.x86_1.6.0.013\jre\lib\rt.jar]
[Loaded java.io.Serializable from D:\Program Files\Genuitec\Common\binary\com.sun.java.jdk.win32.x86_1.6.0.013\jre\lib\rt.jar]
[Loaded java.lang.Comparable from D:\Program Files\Genuitec\Common\binary\com.sun.java.jdk.win32.x86_1.6.0.013\jre\lib\rt.jar]
[Loaded java.lang.CharSequence from D:\Program Files\Genuitec\Common\binary\com.sun.java.jdk.win32.x86_1.6.0.013\jre\lib\rt.jar]
[Loaded java.lang.String from D:\Program Files\Genuitec\Common\binary\com.sun.java.jdk.win32.x86_1.6.0.013\jre\lib\rt.jar]
[Loaded java.lang.reflect.GenericDeclaration from D:\Program Files\Genuitec\Common\binary\com.sun.java.jdk.win32.x86_1.6.0.013\jre\lib\rt.jar]
[Loaded java.lang.reflect.Type from D:\Program Files\Genuitec\Common\binary\com.sun.java.jdk.win32.x86_1.6.0.013\jre\lib\rt.jar]
[Loaded java.lang.reflect.AnnotatedElement from D:\Program Files\Genuitec\Common\binary\com.sun.java.jdk.win32.x86_1.6.0.013\jre\lib\rt.jar]
[Loaded java.lang.Class from D:\Program Files\Genuitec\Common\binary\com.sun.java.jdk.win32.x86_1.6.0.013\jre\lib\rt.jar]
从以上信息你基本上可以看到一个在执行的时候加载的相关类及jar,明白类在什么时候被加载。以上都是类加载器classloader的工作(这是一份没有小费的工作),getCallerClassLoader()是类加载器的一个私有方法,所以我们是没办法直接调用的,所以在运行中我们应该通过当前 类的实例.getClass().getClassLoader(); getCallerClassLoader()这个方法的调用其实是暗箱操作如果你玩过地下柳河菜或者都求的话,你懂得! 在new一个实例的时候这个方法会被默认调用。
在这里我纠正一个常识,静态初始化区域并不是在类第一次载入才会被加载,而是在第一次初始化时被加载并且只有一次。我们测试一下,下面是测试代码:
package com.ec.test.classloader.test; public class C { /** * @param args * @throws ClassNotFoundException */ public static void main(String[] args) throws ClassNotFoundException { // TODO Auto-generated method stub //将true改成false在测试一下 Class.forName("com.ec.test.classloader.test.B", true, new C().getClass().getClassLoader()); } } class B{ static{ System.out.println("test静态初始化区域"); } }
我们看一下Class.forName()的api
* @param name fully qualified name of the desired class
* @param initialize whether the class must be initialized
* @param loader class loader from which the class must be loaded
* @return class object representing the desired class
第二个参数 initialize 布尔类型,判断该类是否必须初始化。当initialize 为true的时候我们就会看到 ”test静态初始化区域“输出 ,相反没有任何输出,
classloader.loadClass("")与 Class.forName("com.cn.B", false, classLoader);一样都没有对类进行第一次初始化
那么类如果在第一次载入的时候没有被初始化,那么在什么时候会初始化呢,答案是在第一次实例化的时候,如果第一次载入已经初始化,那么第一次实例化还会初始化吗,其实前面我们已经说到了,在第一次初始化时被加载并且只有一次。也就是说第一次载入的话已经被初始化了那么第一次实例化的时候是不会被再次初始化的,下面我们验证一下修改上面的代码
package com.ec.test.classloader.test; public class C { /** * @param args * @throws ClassNotFoundException * @throws IllegalAccessException * @throws InstantiationException */ public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { // TODO Auto-generated method stub //将true改成false在测试一下 System.out.println("==========开始载入类B=========="); Class c=Class.forName("com.ec.test.classloader.test.B", true, new C().getClass().getClassLoader()); System.out.println("==========载入类B结束=========="); System.out.println("==========开始实例化B=========="); Object o=c.newInstance(); System.out.println("==========实例化B结束==========="); //new C().getClass().getClassLoader().loadClass("com.ec.test.classloader.test.B"); } } class B{ static{ System.out.println("test静态初始化区域"); } }
下面我们来看一下initialize 为true的时候输出结果为
==========开始载入类B==========
test静态初始化区域
==========载入类B结束==========
==========开始实例化B==========
==========实例化B结束===========
下面我们来看一下initialize 为false的时候输出结果为
==========开始载入类B==========
==========载入类B结束==========
==========开始实例化B==========
test静态初始化区域
==========实例化B结束===========
好了通过结果你懂得。 在这里在说一点我们采用new操作的时候会自动初始化和实例化该类,这个暗箱操作也许是我们长久没发现这个问题的原因。