一、概念
程序编译后,生成class文件,经过加载、验证、准备、解析、初始化,最终使程序可以被JVM识别。
二、类的生命周期
类从被加载到虚拟机内存开始,到卸载出虚拟机内存结束,一共经历加载、验证、准备、解析、初始化、使用、卸载七个阶段。
其中验证、准备、解析统称为连接。
借用网上的图:
其中解析过程在某些情况下可以在初始化之后执行,这是为了支持JAVA的动态绑定。
三、类的初始化时机
1. 使用new实例化对象
2. 反射
3. 初始化一个类时,发现父类没有被初始化,先初始化父类
4. 指定要执行的主类包含main方法
5. 使用JDK1.7的动态语言支持时,MethodHandle实例包含REF_get\put\invoke static句柄,且对应类未初始化
以上场景行为称为对类的主动引用,而被动引用不会被初始化,包含以下几个场景:
1. System.out.print(SubClass.value); // value的引用在父类SuperClass中
2. SuperClass[] sca =new SuperClass[10]; // 数组类型不会触发类的初始化
数组类型是jvm自动生成、继承Object的子类。
3.System.out.print(Constants.HELLOWORLD); // 常量类在编译时将进入常量池,本质上不会调用Constants类的初始化。
四、类加载过程
1. 加载
2. 验证
0xCAFEBABE
开头、class文件版本号等3. 准备
static修饰的变量将被设置为零值。
public static int value = 123; // 在准备阶段将被赋0
public static final int value = 123; // 如果是常量,将直接初始化,而不是0,因为final的意思是:不可变的。
4. 解析
符号引用->直接引用
5. 初始化
初始化阶段才开始真正的执行JAVA代码,该阶段是虚拟机执行类构造器<clinit>()方法的过程。
<clinit>()方法有以下特点:
public class Test { static { i = 0; // 给变量赋值可以正常编译通过 System.out.print(i); // 这句编译器会提示“非法向前引用” } static int i = 1; }
static class Parent { public static int A = 1; static { A = 2; } } static class Sub extends Parent { public static int B = A; } public static void main(String[] args) { System.out.println(Sub.B); // 输出结果是父类中的静态变量 A 的值,也就是 2。 }
不为该类生成<clinit>()方法。
阻塞,所以<clinit>有耗时操作时将会造成线程阻塞。
原文:https://www.cnblogs.com/bloodthirsty/p/12502799.html