目录
5. JVM类的生命周期
5.1. 类的生命周期-概述
5.2. 类的生命周期-加载阶段
5.3. 类的生命周期-连接阶段
5.3.1. 连接阶段-验证阶段
5.3.2. 连接阶段-准备阶段
5.3.3. 连接阶段-解析阶段
5.4. 类的生命周期-初始化阶段
5.4.1. 初始化阶段-工作流程
5.4.2. 初始化阶段-触发情况
5.4.3. 初始化阶段-无clinit方法情况
5.4.4. 初始化阶段-父子类初始化情况
5.5. 类的生命周期-面试题
5.5.1. 1
5.5.2. 2
5. JVM类的生命周期
5.1. 类的生命周期-概述
- 类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过
- 类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析
5.2. 类的生命周期-加载阶段
- 加载阶段的作用:加载字节码文件,给类分配内存空间
- 加载阶段的步骤:
-
- 类加载器根据类的全限定名以二进制流方式将字节码文件加载到运行时数据区(字节码文件来源不同:本地文件,动态代理生成,网络传输)
- JVM将加载完毕的字节码信息保存到方法区中,JDK8保存在元空间中
- 方法区生成一个instanceKlass对象,其中保存了类所有的信息,还包含实现多态的信息等
- 同时堆中生成一个类似instanceKlass对象的java.lang.Class对象,通过Class对象反射获取类的信息以及存储静态字段的数据,这样就限制了开发者的访问范围
5.3. 类的生命周期-连接阶段
5.3.1. 连接阶段-验证阶段
- 验证阶段的作用:JVM验证字节码文件的正确性,包括语法校验、语义校验、字节码验证等步骤。验证过程保证被加载的类是合法、符合规范的,没有安全方面的问题
- 验证阶段验证部分:
-
- 文件格式验证:比如文件是否以OxCAFEBABE开头,主次版本号是否满足当前JaVa虚拟机版本要求。
- 元信息验证:例如类必须有父类
- 验证程序执行指令的语义:比如方法内的指令执行到一半强行跳转到其他方法中去
- 符号引用验证:例如是否访问了其他类中private的方法等
- 文件格式验证:比如文件是否以OxCAFEBABE开头,主次版本号是否满足当前JaVa虚拟机版本要求。
5.3.2. 连接阶段-准备阶段
- 准备阶段的作用:JVM为被加载的类的静态变量分配内存,并设置默认初始值(用户指定的值赋值发生在初始化阶段),被final修饰的静态变量会在准备阶段直接赋值
- 常见Java类型的初始值
5.3.3. 连接阶段-解析阶段
- 解析阶段的作用:JVM将常量池内的符号引用转换为直接引用,以便于之后的访问和调用
5.4. 类的生命周期-初始化阶段
- 初始化阶段的作用:JVM对类初始化,包括静态变量赋值,静态代码块执行等,同时保证初始化过程是线程安全的
- 初始化阶段执行的字节码方法部分:初始化阶段会执行字节码文件中clinit部分的字节码指令
5.4.1. 初始化阶段-工作流程
- 初始化阶段工作流程:字节码指令执行顺序跟Java代码编写顺序一致
-
- iconst_1:将常量1压入栈中.
- putstatic#2:将栈中的数据弹出给常量池中的静态变量赋值
- iconst_2:将常量2压入栈中.
- putstatic#2:将栈中的数据弹出给常量池中的静态变量赋值
5.4.2. 初始化阶段-触发情况
- 类初始化阶段被触发的情况:
-
- 访问一个类不被final修饰的静态变量或静态方法
- 调用Class.forName(String className)指定类
- 使用new创建一个类的实例
- 执行main方法的当前类
- 访问一个类被final修饰但是需要执行指令才能得出结果的静态变量
- --XX:+TraceClassLoading,打印已经初始化的类
5.4.3. 初始化阶段-无clinit方法情况
- 类初始化阶段触发但没有clinit方法的情况:
-
- 没有静态代码块和静态变量赋值语句
- 有静态变量的声明,但没有复制语句
- 有静态变量的声明,但用final关键字修饰(会在准备阶段赋值)
- 创建该类的对象数组不会触发该类的初始化
5.4.4. 初始化阶段-父子类初始化情况
- 子类不触发情况:直接访问父类的静态变量是不会触发子类初始化的
- 父子都触发情况:子类的初始化clinit方法调用之前会先调用父类的clinit方法
5.5. 类的生命周期-面试题
5.5.1. 1
- 这个程序执行的结果是什么?
- 程序解题思路:
- 判断触发类的初始化情况:该类中执行main方法,所以该类会进行舒适化阶段
- 查看该类的字节码:字节码命令会告诉你代码块和构造方法的执行顺序
-
- clinit字节码(初始化方法):在类的初始化时执行,初始化阶段执行静态代码块和给静态变量赋值
- init字节码(构造方法):在类每次被创建实例时执行,执行代码块和构造方法,从字节码指令中可以看出,代码块打印C是先于构造方法打印B的
- main字节码:由于该类调用main方法先进行初始化,在执行打印A,然后创建两次Demo实例,也就意味着调用两次代码块和构造方法
- clinit字节码(初始化方法):在类的初始化时执行,初始化阶段执行静态代码块和给静态变量赋值
- 最终的结果就是DACBCB
5.5.2. 2
- 这个程序执行的结果是什么?
- 程序解题思路:
-
- 调用B的clinit方法之前会先父类A的clinit方法
- a的值从0,到1,最后到2,最终打印结果也是2
- 如果把new B()去掉它的执行结果是什么?
- 程序解题思路:
-
- 直接访问B类的静态变量时不会进行子类的初始化
- 打印结果为1