Java 类加载的生命周期:
- Loading,加载字节流
- Linking,链接
- Verification,校验合法性
- Preparation,准备阶段,分配内存,初始值
- Resolution,解析阶段,常量池替换引用,变成直接引用
- Initialization 初始化,调用类构造器
- Using 使用阶段
- Unloading,卸载
其中第二步到第四步,验证,准备,解析统一称为 Linking 过程。
在前5个阶段,加载、验证、准备和初始化四个阶段顺序是确定的,但是解析阶段不一定。某些情况下会在初始化阶段之后开始,为了支持 Java 语言的运行时绑定(动态绑定)。
这几个阶段是按顺序开始,而不是按顺序完成的,这些阶段通常是交叉进行的,通常会在一个阶段执行的过程中调用或激活另一个阶段。
Loading
加载阶段是查找并加载类的二进制数据,在加载阶段,JVM 需要完成:
- 通过类的全限定名获取二进制字节流
- 将字节流的静态存储结构转化为方法区的运行时数据结构
- 内存中生成一个代表类的
java.lang.Class对象,作为方法区这个类的各种数据的访问入口
加载阶段是可控最强的阶段,开发人员可以使用系统的类加载器,或自定义类加载器完成加载。
加载完成,虚拟机外部的二进制字节流就按照虚拟机的格式存储在方法区了,而且在堆内存中也有一个 java.lang.Class 类对象。
获取字节流的方式
- zip 包,JAR,WAR,EAR
- 网络,Applet
- 运行时,动态代理
- 其他文件, JSP
- 数据库等等
Verification
验证阶段会确保 Class 字节流符合虚拟机要求。
并且不会堆虚拟机产生安全问题。
验证阶段大致分成4个步骤:
- 文件格式验证:验证字节流是否符合 Class 文件格式规范,比如是否以
0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围内 - 元数据验证:对字节码描述的信息进行语义分析保证描述的信息符合 Java 语言规范,比如这个类是否有父类
- 字节码验证:确定语义是否合法符合逻辑
- 符号引用验证:确保解析动作能正确执行
文件格式
- Magic Number, 0xCAFEBABE
- Major, Minor Version 主次版本号
- 常量池中常量是否有不被支持的类型
- 索引值
- CONSTANT_Utf8_info 常量
- Class 文件
元数据校验
- 父类,
java.lang.Object - 父类是否继承了不允许修改的类,final
- 如果不是抽象类,是不是实现了所有要求的方法
- 类字段、方法是否有矛盾
字节码验证
- 数据栈的类型和指令代码相吻合
- 跳转指令不会跳转到方法体外
- 方法体的类型转换
符号引用验证
- 符号引用中通过字符串能否找到类
- 指定类中符合方法的字段描述符
- 访问属性
准备
准备阶段为类变量分配内存,设置初始值。
- 内存在方法区分配被 static 修饰的变量,不包括实例变量,实例变量会在对象实例化时随对象一块分配在 Java 堆中
假如有类变量定义 public static int value = 1 。
那么变量 value 在准备阶段初始值是 0,而不是 1,把 value 赋值为 1 的 public statci 指令是在程序编译之后,存放Zeal类构造器方法中,把 value 赋值为 3 的动作在初始化阶段才会执行。
但是如果有定义 private static final int value = 3,也就是定义了 Constant 变量,同时被 final 和 static 修饰,那么在准备阶段变量 value 就会被初始化为指定的值。
解析
虚拟机将常量池内的符号引用替换为直接引用的过程。
解析的动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类符号引用。
类或接口的解析
初始化
初始化为类的静态变量赋予初始值,JVM 负责堆类进行初始化,对类变量进行初始化。
Java 中堆类变量进行初始值设定有两种方式:
- 声明类变量是指定初始值
- 使用静态代码块为类变量指定初始值
JVM 初始化步骤:
- 假如类没有被 Loading 和 Linking,程序会先 Loading 和 Linking 该类
- 假如该类的直接父类没有初始化,则先初始化其直接父类
- 假如类中有初始化语句,则系统依次执行这些初始化语句
类初始化时机:只有当对类的主动使用时才会导致类初始化,包括如下:
- 类创建实例,
new方法 - 访问某个类或接口的 静态变量,或对该静态变量赋值
- 调用类的静态方法
- 反射(比如
Class.forName("com.einverne.Test"); - 初始化类的子类,父类也会被初始化
- Java 虚拟机启动时被标明为启动类的类,直接使用
java.exe命令来运行某个主类
结束生命周期
在如下几种情况下,Java 虚拟机将结束生命周期:
- 执行 System.exit() 方法
- 程序正常执行结束
- 程序在执行过程中遇到了异常或错误而异常终止
- 操作系统出现错误而导致 JVM 进程终止