JVM类生命周期:
- 加载阶段:由ClassLoader来完成,ClassLoader将字节码以流的形式加载进内存,在元空间(方法区)生成类模板,并且在堆中生成其Class对象,做为访问元空间中类模板信息的接口。ClassLoader在整个类的加载过程(加载阶段,链接阶段,初始化阶段),只能影响到类的加载,无法通过Classloader去改变链接和初始化阶段。
- 链接阶段:1->验证(与加载阶段同时进行),验证字节码格式是否正确,语法分析等,2->准备,静态变量默认初始化,静态常量显式初始化(仅仅针对使用字面量的初始化),3->解析(在初始化阶段之后执行),符号引用转为直接引用,因为此时所有需要的类已经被加载到内存,有了具体的地址,这时就可以把符号引用变成直接引用。
- 初始化阶段,静态变量初始化(类中的字段),编译器生成clinit()方法,并且在初始化阶段执行(开始执行字节码指令了)。
- 用户使用上述加载的类,写具体的业务代码
- 类的卸载
本文研究类加载过程中的初始化阶段,主要就是类静态变量的显式赋值。
分为两种情况:
1.链接->准备 阶段的显式赋值
满足的条件:
- 静态常量 static final
- 用 字面量 进行显示赋值,字面量(保存在常量池中) 可以认为是有确定值的基本数据类型,还有具有确定值的string.
2.初始化阶段 的显式赋值
不满足1条件的 静态变量或者静态常量(非字面量) 显式赋值,都要在初始化阶段的clinit()方法中进行显式赋值
当然了,如果没有静态变量显式赋值,或者静态常量显式赋值(非字面量) ,则编译器不会生成clinit()方法。
再举例说明:
public static final String s1 = new String("helloWorld1");
public static final int NUM1 = new Random().nextInt(10);
上面的两个静态常量显式赋值,是在初始化阶段进行的,因为不是用字面量进行赋值的,还有另外一种理解,因为需要new对象,即执行代码,而最开始执行代码的地方,就是初始化阶段的clinit()方法,所以可以认为上面的显示赋值是在初始化阶段进行的。
Note:
- 非静态方法的局部变量表的角标0,存放的是this指针,指向当前对象。
- 静态方法的局部变量表的角标0,不存放this指针,因为静态方法是类级别的。