一. 类的生命周期
类从被加载到虚拟机内存中开始,到卸载出内存为止,有以下(如图)的生命周期:
以上“加载->验证->准备->解析->初始化”称为类的加载过程。
Java虚拟机规范中没有对什么时候需要开始类加载的第一阶段进行强制约束,而是交给了虚拟机根据具体实现来自由把握。
但是对于初始化阶段,虚拟机有以下5种必须对类立即进行“初始化”的情况:
(1)遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类还没初始化就需要马上触发其初始化。常见场景:使用new实例化对象、读取或设置一个类的静态字段(除了编译器处理了的final字段)以及调用一个类的静态方法时。
(2)使用反射(java.lang.reflect包)对类进行调用时,也会触发类的初始化。
(3)当初始化一个类时,发现父类还未初始化时,要先触发父类的初始化。
(4)当虚拟机启动时,用户需要指定一个执行包含main方法的主类,虚拟机会初始化这个类。
(5)当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,触发对应的这个类的初始化。
以上5种场景的行为称为对一个类进行主动引用,除此之外的引用方式将不会触发类的初始化,称为被动引用。
以下是被动引用的一个列子:
class SuperClass
{
static
{
System.out.println("SuperClass init!");
}
public static int value = 123;
}
class SubClass extends SuperClass
{
static
{
System.out.println("SubClass init!");
}
}
public class NotInitialization
{
public static void main(String[] args)
{
System.out.println(SubClass.value);
}
}
输出结果:
SuperClass init!
123
二.类的加载过程
1.加载
加载阶段进行过程:
(1)通过一个类的全限定名获取定义此类的二进制字节流;
(2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
(3)在内存中生成一个代表这个类的Java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
2.验证
验证是为了确保字节流包含信息符合当前虚拟机的要求,并且是安全的。
大概有四个验证阶段
(1)文件格式验证:验证字节流是否符合Class文件格式的规范;
(2)元数据验证:对字节码描述的信息进行语义分析,目的是对类的元数据信息(数据类型)进行语义校验;
(3)字节码验证:对类方法体进行校验分析;
(4)符号引用验证:对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。
这里可以做一个优化:因为验证阶段耗时还是挺大的,假如已被重复使用多次并且是验证过的代码即可跳过这个步骤,加快类加载速度。
3.准备
正式为类变量(static)分配内存和设置类变量的初始值,这里的类变量除了被final修饰的。
4.解析
将符号引用替换成直接引用的过程。
5.初始化
执行()类构造方法的过程,即static块语句的执行和变量的复制操作。