Java类加载的生命周期
加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载
加载、验证、准备、初始化和卸载 这五个阶段的顺序是确定的。
Java类加载的时机
- 遇到 new、getStatic、setStatic、invokeStatic这四条字节码指令的时候。
(使用new关键字初始化实例对象、读取/设置一个类的静态字段(被final修饰、已经在编译器放入常量池的静态字段除外)、调用一个类的静态方法) - 使用 java.lang.reflect进行反射调用的时候。
- 当初始化一个类的时候,发现其父类还没有初始化,那么先去初始化它的父类。
- 虚拟机启动的时候,需要初始化main函数所在的类。
上面的四种场景的行为被称为对一个类的主动引用。除此之外的引用类的方式
Java类的加载
Java类的加载是Java类加载过程的第一个阶段,需要做的事情有:
-
通过一个类的全限定名来获取定义此类的二进制流。
(获取的方式,可以是zip(jar包、EAR/WAR格式)的包、网络中获取、JSP文件生成等) -
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
(方法区就是用来存放已加载的类信息、常量、静态变量、编译后的代码的运行时区域) - 在Java堆中生成一个代表这个类的java.lang.class对象,作为方法区这些数据的访问入口。
Java类的连接
1. 验证
确保Class文件的字节流中包含的信息符合当前的虚拟机的要求,并且不会危害虚拟机自身的安全。
主要包含以下四个阶段:
- 文件格式验证:验证字节流是否符合Class文件格式的规范,并且能被当前的虚拟机处理。
- 元数据验证:对字节码描述的信息进行语义分析,确保描述的信息符合Java语言规范的要求。
- 字节码验证:对数据流与控制流进行分析,确保被检验类的方法在运行时不会做出危害虚拟机安全的行为。
- 符号引用验证:对类自身以为(常量池中的各种符号引用)的信息进行匹配性的校验。
2. 准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段
注意:
首先是这时候进行内存分配的仅包括类变量(被static修饰的变量),不包括实例变量。实例变量将会在对象实例化时随着对象一起分配在Java堆中。
其次这里所说的初始值通常情况下是数据类型的零值。
这里示例讲解一下通常情况:
假设一个类变量的定义为:
public static int value = 123;
那么变量value在准备阶段过后的初始值为0而不是123,因为这时候尚未开始执行任何Java方法,而把value赋值为123的putstatic指令是程序被编译后,存放与类构造器<clinit>()方法之中,所有把value赋值为123的动作将在初始化阶段才会被执行。
特殊情况:
如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量value就会被初始化为ConstantValue属性所指定的值。
public static final int value = 123;
编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123。
3. 解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
- 类或接口的解析
- 字段解析
- 类方法解析
- 接口方法解析
Java类的初始化
类初始化阶段是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与外,其余动作完全由虚拟机主导和控制。初始化阶段才真正执行类中定义的Java程序代码(或者说是字节码)。
在准备阶段,变量已经赋过一次系统要求的初始值。
初始化阶段是执行类构造器<clinit>()方法的过程。
主要阅读《深入理解Java虚拟机》一书