Java虚拟机类加载机制
概述
虚拟机把描述类的数据从 Class 文件加载到内存,井对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制。
类加载的时机
类从被加载到虚拟机内存中开始,到卸栽出内存为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用和卸载七个阶段。其中验证、准备和解析三个部分统称为连接。
加载
通过一个类的全限定名来获取定义此类的二进制字节流。
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
会在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个 Class 文件获取,这里既可以从 ZIP 包中读取(比如从 jar 包和 war 包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将 JSP 文件转换成对应的 Class 类)
链接
验证
这一阶段的主要目的是为了确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求,保证被加载类的正确性,不会危害虚拟机自身的安全。验证主要包含4个阶段的校验动作:文件格式验证、元数据验证、字节码验证、符号引用验证。
准备
准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。注意这里所说的初始值概念,比如一个类变量定义为:
1 | public static int value = 123; |
实际上变量 value 在准备阶段过后的初始值为 0 而不是 123,将 value 赋值为 123 的 putstatic 指令是程序被编译后,存放于类构造器方法之中。
但是注意如果声明为:
1 | public static final int value = 123; |
在编译阶段会为 value 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 value 赋值为 123。
解析
解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是在 class 文件中以: CONSTANT_Class_info、CONSTANT_Field_info
、CONSTANT_Method_info等类型的常量出现。
初始化
初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由 JVM 主导。到了初始阶段,才开始真正执行类中定义的 Java 程序代码。
那么,什么时候开始初始化?
使用 new 该类实例化对象的时候;
读取或设置类静态字段的时候(但被final修饰的字段,在编译器时就被放入常量池的静态字段除外static final);
调用类静态方法的时候;
使用反射 Class.forName(“xxxx”) 对类进行反射调用的时候,该类需要初始化;
初始化一个类的时候,有父类,先初始化父类(注:1. 接口除外,父接口在调用的时候才会被初始化;2.子类引用父类静态字段,只会引发父类初始化);
被标明为启动类的类(即包含main()方法的类)要初始化;
当使用 JDK1.7 的动态语言支持时,如果一个 java.invoke.MethodHandle 实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
以上情况称为对一个类进行主动引用,且有且只要以上几种情况需要对类进行初始化。