JVM类加载过程学习总结
先不说JVM类加载的原理,先看实例:
NormalTest类,包含了一个静态代码块,执行的任务就是打印一句话。
/** * 在正常类加载条件下,看静态代码块是否会执行 * @author jianying.wcj * @date 2013-6-21 */ public class NormalTest { static { System.out.println("hello world!"); } }TestStatic类, 有三行代码,其中两行被注释,测试过程是,在执行其中任意一行代码的时候,注释掉其余两行。
public class TestStatic { public static void main(String[] args) throws ClassNotFoundException { /** * 实验1 */ Class.forName("NormalTest"); /** * 实验2 */ //NormalTest nt = new NormalTest(); /** * 实验3 */ //TestStatic.class.getClassLoader().loadClass("NormalTest"); } }测试的输出的结果是: 在执行 Class.forName("NormalTest")的时候,输出了“Hello world!”,在执行NormalTest nt = new NormalTest();的时候也输出了“Hello world!” 但是在执行代码TestStatic.class.getClassLoader().loadClass("NormalTest");却没有输出“Hello world!” 下面分析一下原因或者说看看这三行代码的内部实现的异同。 以上三行代码其实在执行的时候都会去加载NormalTest.class,这里可以不准确的说以上三行代码是三种加载类的方式。从实验的输出来看,可以确定实验1 和实验2 在加载NormalTest的时候执行了静态代码块,而实验3 直接调用ClassLoader来loadclass的时候没有执行静态代码块。执行静态代码块的过程其实就是初始化类的过程,话说到这,说白了,前两种方式加载类的时候对类进行了初始化,而第三种没有,那么看看部分代码的实现。
public static Class<?> forName(String className) throws ClassNotFoundException { return forName0(className, true, ClassLoader.getCallerClassLoader()); }上面这段代码是Class.forName()的定义,实现里直接调用了forName0(),forName0的方法签名是:
private static native Class forName0(String name, boolean initialize, ClassLoader loader)throws ClassNotFoundException;这是个本地方法,本地方法的C++实现先不研究(抛砖引玉一下,谁有好的研究可以分享一下),这个本地方法第二个参数是initialize,这个参数的true或false就是告诉虚拟机,在根据类的全限定名name加载类的时候,要对类进行初始化。在forName调用forName0的时候,看以看到initialize设置成了true,所以我们的类的静态代码块就被执行了(类被初始化了)。 实验2在new一个对象的时候,也会在加载类的时候触发其初始化方法,这个的实现在虚拟机实现的C++代码里,JVM虚拟机规范指出,在执行new指令创建一个对象的时候要对加载的类进行初始化(《深入理解java虚拟机》第七章有说)。实验3在执行ClassLoader.load一个class的时候属于被动加载类,根据虚拟机规范不会对类进行初始化。对于new和ClassLoader.load加载类的方式,在java代码层面已经看不到是否需要对类进行初始化的标志了,内部实现在JVM的C++实现中(C++实现逻辑待哥们的水平提高提高再做分析总结)。
上面都是根据实例的总结,下面来点官方的资料学习总结。
首先看下关于类加载的时候是否初始化的虚拟机规范:
虚拟机规范则是严格规定了有且只有四种情况必须立即对类进行初始化(而加载、验证、准备自然需要在此之前开始):
1) 遇到new 、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行初始化,则需要先触发其初始化。生成这4条指令的最常见的java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
2) 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
3) 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
4) 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
可见:当满足上述4中条件之一的任何一种情况都会执行类的静态代码块,而除上述4中情况外,则不会对类的初始化(注意加粗的有且只有4个字)。
2.类从加载到卸载,整个过程可以描述为7个阶段:
加载:虚拟机需要完成以下三件事情:
a) 通过一个类的权限定名来获取此类定义的二进制字节流。
b) 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构
c) 在java堆中生成一个代表这个类的java.lang.Class对象,作为访问方法区的入口
验证: