前言
这几天简单阅读了《深入理解Java虚拟机》的类加载章节,简单总结了一些内容,类加载器的部分之后会补充🖊
类的生命周期
加载→验证→准备→解析→初始化===>使用===>卸载
加载
(不要和类加载搞混)加载阶段虚拟机所完成的3件事
- 通过类名定义此类的二进制字节流
- 将静态结构存储结构转化进方法区的运行时数据结构
- 生成java.lang.Class对象,存入方法区,作为该类入口
验证
通过验证确保Class文件的字节流包含的信息符合当前虚拟机的要求,确保其不会威胁虚拟机自身
准备
正式为类分配方法区内存空间(不是指对象),并设置类变量初始值(0值)。(这里仅仅分配static类变量,实例变量在实例对象时加入堆)
解析
将常量池的符号引用替换为直接引用
初始化
这是类加载的最后一步,这个阶段才真正开始执行代码,执行类构造器<clinit> ()
(由所有类变量赋值操作和静态语句块合并成)。
- 类构造器的合并是按语句出现顺序排序的,静态语句块只能访问到定义在静态语句块之前的变量。(定义在其之后的变量只能赋值不能访问)如下所示
1 | public class Test { |
- 父类的类构造器一定会先执行完毕于子类的类构造器(也意味着其静态语句块先于子类的变量赋值操作)
- 接口没有静态语句块,但还有变量初始化的赋值操作,也会生成\
()方法,但其不需要先执行父接口的类构造器(因为接口只有真正引用到了父接口时,父接口才会初始化)
类加载的时机
类中有且仅有4种主动引用情况执行类初始化,其他情况(被动引用)都不会初始化类
- 遇到new、getstatic、putstatic、invokestatic这4个字节码指令
- new:使用new关键字实例化对象。
- getstatic:读引用一个类的静态字段。(已经在编译期把结果放入常量池的final静态字段除外)
- putstatic:写引用一个类的静态字段。(已经在编译期把结果放入常量池的final静态字段除外)
- invokestatic:调用一个类的静态方法。(已经在编译期把结果放入常量池的final静态常量除外)
- 初始化类时,若父类也未初始化,先初始化父类
- 接口初始化:只有真正引用到了父接口时,才会初始化。
- 反射调用类
- 虚拟机启动时会先初始化包含main()方法的类
演示几个不会触发类初始化的被动引用示例:
下面有三个示例
- 通过类引用final静态常量,因为final静态常量已经在编译期进入常量池,本质上不会调用到类,不触发初始化
- 通过子类引用父类的静态字段,不会导致子类初始化
- 数组声明不触发初始化
1 | public class SuperClass { |
1 | public class test1 { |
1 | public class test2 { |
1 | public class test3 { |