一个类从被加载到JVM内存中开始到卸载为止,他的整个生命周期将会经历加载、验证、准备、解析、初始化、使用和卸载七个阶段。其中加载、验证、准备、初始化和卸载的顺序是固定的。
一.触发类加载时机
关于什么时候触发类加载第一个步骤“加载”,《java虚拟机规范》中没有强制约束,但是对于什么时候触发“初始化”阶段,《java虚拟机规范》中明确指出有且只有一下六中情况必须触发“初始化”
- 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始化,则需要触发初始化阶段,能够触发这四条字节码指令的java代码有:
- 使用new关键字实例化对象的时候;
- 读取或设置一个类型为静态字段的时候;
- 调用一个静态方法的时候。
- 使用java.lang.reflect包下通过反射调用对象的时候;
- 初始化的时候如果发现其父类没有初始化,则需要现触发其父类先初始化;
- 在虚拟机启动的时候,如果用户指定了要执行的主类,虚拟机需要先初始化这个主类;
- 当使用JDK7新加入的动态语言支持时,如果一个Java.lang.invoke.MethodHandle实例最后解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄时,并且这个方法句柄对应的类没有进行初始化,则需要现触发其初始化;
- 当一个接口定义了JDK8新加入的默认方法(被default关键字修饰的接口方法)时,如果这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
二、类加载过程
1.加载
- 通过类的全限定名来获取类的二进制字节流;
- 将字节流所代表的静态存储结构转换为方法区的运行时数据结构;
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的数据访问接口;
对于数组加载过程如下:
- 如果数组内元素类型是引用类型,那就递归如上加载步骤加载数组内元素,并将数组标识在加载该类型的类加载器的类名称空间上;
- 如果数组内元素类型不是引用类型(如:int[]),则会将当前数据与启动类加载器关联;
- 数组类的访问性与他的元素类型一致,如果元素类型为public那么这个数组也可被其他的类和接口访问。
2.验证
验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被运行后不会危害虚拟机安全。
1). 文件格式验证
第一阶段要验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理;
2). 元数据验证
第二阶段主要是对字节码描述进行语义校验,已保证其描述的信息符合《Java虚拟机规范》的要求;
3). 字节码验证
第三阶段主要是通过数据流分析及控制流分析,确定程序语义是否合法,这一阶段只要是对类的方法体进行分析;
4). 符号引用验证
最后一个阶段主要是对类自身以外的各类信息进行匹配校验。
3.准备
将静态变量分配内存并设置默认值,如果当前静态变量为final修饰,则赋的是原始值
final修饰的常量是在编译期生成constantValue属性,在准备阶段虚拟机会根据constantValue的设置将value设置为实际的值。
4.解析
解析阶段是Java虚拟机将常量池内的符号引用转换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符号。
- 符号引用:是用一组符号来描述引用的目标,符号可以是任何形式的字面量,只要是使用时能无歧义的定位到目标即可,** 符号引用与虚拟机的内存布局无关,引用的目标并不一定是已经加载到虚拟机内存中的内容。**
- 直接引用: 直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。直接引用是和虚拟机内存布局直接相关的,同一个符号引用在不同的虚拟机上翻译出来的直接引用一般不会相同,如果有了直接引用,那引用目标必定在虚拟机内存中存在。
1). 类或接口的解析
假设当前代码所处的类为D,如果把一个从未解析过的符号引用N解析为一个类或接口C的直接引用,那虚拟机完成整过解析过程需要包括一下3步:
①. 如果C不是一个数组类型,那么虚拟机将会把N代表的全限定名传递给D的类加载器去加载C;在加载过程中,会经过验证步骤,一旦加载过程出现失败,则解析过程就宣告结束。
②. 如果C是一个数组类型,且元素类型为对象,那将会按照① 的规则加载数组元素类型; 如果元素类型为8种基本类型,则有虚拟机生成一个代表该数组维度和元素的数组类型。
③. 如果前面两步都成功,判断是否D具备对C的访问权限