Java类加载的过程
一个类型从被加载到虚拟机内存中,到卸载出内存位置,它的整个生命周期将会经历加载、验证、准备、解析、初始化、使用、卸载七个阶段。其中验证、准备、解析三个部分统称为连接。
加载、验证、准备、初始化、和卸载这个5个阶段的顺序是确定的,类型的加载过程必须按照这种顺序按部就班的开始。注意的是,这5个阶段通常都是互相交叉地混合进行,会在一个阶段执行的过程中调用、激活另一个阶段,所以是“开始”,而不是“进行”或者“完成”。
解析阶段在某些情况下(如为了动态绑定特性),可以在初始化之后再进行。
《Java虚拟机规范》翻严格规定了有且只有6种情况必须立即对类进行“初始化”(而加载、验证、装备自然需要在此之前进行):
- 遇到
new
、getstatic
、putstatic
或invokestatic
这四条字节码指令时,若类型没有初始化,则需先触发其初始化阶段 - 使用
java.lang.reflect
包的方法对类型进行反射调用时 - 当初始化类,发现其父类还没有进行初始化,则需先触发其父类的初始化
- 虚拟机启动时,用户需要指定一个要执行的主类(main()方法所在的类),虚拟机会先初始化这个主类
- (JDK7新加入的动态语言支持)如果一个
java.lang.invoke.MethodHandle
实例最后的解析结果为REF_getStatic
、REF_putStatic
、REF_invokeStatic
、REF_newInvokeSpecial
四种类型的句柄,且这个方法句柄对应的类没有初始化,则需要先触发其初始化 - 一个接口中定义了默认方法(被default关键字修饰的接口方法),如果这个接口的实现类发生了初始化,那该接口要在其之前初始化
加载
加载阶段,Java虚拟机需要完成以下三件事情:
- 通过一个类的全限定名来获取此类的二进制字节流
- 将这个字节流所代表的静态存储接口转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的
java.lang.Class
对象,作为方法区这个类的各种数据的访问入口
加载阶段和连接阶段的部分动作(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始了。
验证
这一阶段的目的是保证Class文件的字节流中包含的信息符合《java虚拟机规范》的全部约束条件,保证这些信息被当做代码运行后不会危害虚拟机自身的安全。验证阶段大致会完成以下四个阶段的检查动作:
-
文件格式验证
第一阶段验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理
-
元数据验证
第二阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合《Java语言规范》的要求
-
字节码验证
第三阶段主要目的是通过数据流分析和控制流分析,确定程序语义是合法的、符合逻辑的
-
符号引用验证
符号引用验证可以看做是对类自身以外(常量池中的各种符号引用)的各类信息进行匹配性校验,通俗的来说就是,该类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源
准备
准备阶段是正式为类中定义的变量(即静态变量、被static修饰的变量)分配内存并设置类变量初始值的阶段。注意,这时候进行内存分配的仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。
假设一个类变量的定义为:
public static int value = 123;
那变量在准备阶段过后的初始值是0而不是123。将value赋值为123是在类的初始化阶段才执行的
解析
解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程
- 符号引用(Symbolic References):符号引用以一组符号类描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可
- 直接引用(Direct References):直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。
初始化
类的初始化阶段是类加载过程的最后一个步骤。在前几个类加载动作中,除了在加载阶段用户应用程序可以通过自定义类加载器的方式局部参与,其余动作完全由Java虚拟机来主导控制。直到初始化阶段,Java虚拟机才开始真正执行类中编写的Java程序代码,将主导权移交给应用程序。
进行准备阶段时,变量已经赋过一次系统要求的初始零值,而在初始化阶段,则会根据程序员通过程序编码定制的主观计划去初始化类变量和其他资源。初始化阶段就是执行类构造器<clinit>()
方法的过程.