类的生命周期
约 1538 字大约 5 分钟
2025-08-29
类的生命周期是 Java 虚拟机(JVM)管理类的核心机制,涵盖了从类被加载到内存到最终被卸载的完整过程。它将经历加载、链接(验证、准备、解析)、初始化、使用、卸载五个阶段。
加载(Loading)
核心任务
加载阶段的核心任务是通过类的全限定名(如 java.lang.String)查找字节码文件(.class),将其二进制数据读入内存,并在方法区生成该类的运行时数据结构(如类元信息、常量池等),同时在堆中创建一个 java.lang.Class 对象作为访问该类数据的入口。
触发条件
类首次“主动使用”时会触发加载,常见的主动使用场景包括:
- 使用
new关键字实例化对象。 - 访问类的静态字段(被
final修饰的编译期常量除外)。 - 调用类的静态方法。
- 使用反射(如
Class.forName())动态加载类。 - 初始化子类时,如果父类尚未加载,会先触发父类的加载。
- 启动类(包含
main()方法的类)。
关键点
- 类加载器(ClassLoader): 负责加载过程,支持自定义加载逻辑(如从网络、加密文件加载)。
- 被动引用不会触发加载: 例如,通过子类引用父类的静态字段,只会触发父类的加载,子类不会被加载。
链接(Linking)
链接阶段分为三个子阶段:验证、准备、解析。
验证(Verification)
验证字节码是否符合 JVM 规范,防止恶意代码破坏虚拟机。包括:
- 文件格式验证: 验证字节码文件的魔数(
0xCAFEBABE)、版本号、长度等是否符合规范。 - 元数据验证: 对类的语义进行校验,如是否存在父类、是否继承了
final类、抽象类的抽象方法是否都已实现等。 - 字节码验证: 分析数据流和控制流,确保程序语义合法,例如类型转换是否安全。
- 符号引用验证: 确保后续解析阶段能正确完成,例如引用的类、字段、方法是否存在且可访问。
准备(Preparation)
为类变量(即被 static 修饰的变量)分配内存并设置初始值(零值)。例如
static常量: 若类变量定义为static int value = 42;,则在此阶段value被初始化为0,而非42(真正的赋值操作在初始化阶段执行)。static final常量: 若类变量定义为static final int VALUE = 42;(即编译期常量),则在此阶段直接赋值为42。
解析(Resolution)
将常量池中的符号引用(Symbolic Reference)替换为直接引用(Direct Reference)。
- 符号引用: 以一组符号来描述所引用的目标,可以是任何形式的字面量,例如将
java.lang.Object这样的字符串表示。 - 直接引用: 直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。
- 解析过程: 针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和动态调用点等符号引用进行。例如,将
java.lang.Object替换为指向方法区中Object类元数据的指针。
初始化(Initialization)
核心任务
执行类构造器 <clinit>() 方法(由编译器自动收集类变量的赋值操作和静态代码块 static{} 中的语句生成),完成类变量的赋值和静态代码块的执行。
触发条件
类的主动使用(同加载阶段),且必须保证父类已初始化。
关键规则
- JVM 保证
<clinit>()方法的线程安全性,在多线程环境下初始化仅执行一次。 - 父类的
<clinit>()方法会优先于子类的<clinit>()方法执行。 - 被动引用不会触发初始化:
- 通过子类引用父类的静态字段,只会触发父类的初始化。
- 通过数组定义引用类(如
MyClass[] arr = new MyClass[10];),不会触发MyClass的初始化。 - 访问类的
final static常量(当常量值在编译期可确定时,视为“编译期常量”),不会触发类的初始化。
使用(Using)
类完成初始化后,进入可用状态,可以进行以下操作:
- 创建对象实例(
new)。 - 访问静态字段或调用静态方法。
- 通过反射调用类的方法或构造器。
- 作为父类被其子类初始化时触发初始化。
卸载(Unloading)
触发条件
当同时满足以下三个条件时,JVM 会回收类的元数据(方法区)及对应的 Class 对象:
- 该类的所有实例已被垃圾回收(堆中无存活对象)。
- 该类的
Class对象未被任何地方引用(如反射、静态变量)。 - 加载该类的
ClassLoader实例已被回收。
常见场景
- 使用自定义类加载器动态加载的类,如热部署场景。
- 若类的
ClassLoader未被回收(如缓存泄漏),则类无法卸载。
类的生命周期阶段总结
| 阶段 | 核心任务 | 关键规则 |
|---|---|---|
| 加载 | 读取字节码,生成 Class 对象 | 类加载器负责,支持自定义逻辑 |
| 链接 | 验证、准备、解析符号引用 | 准备阶段仅赋零值,解析可能延迟到初始化后 |
| 初始化 | 执行 <clinit>(),完成静态赋值 | 父类优先,线程安全,被动引用不触发 |
| 使用 | 通过实例化、反射等方式使用类 | 类的功能完全可用 |
| 卸载 | 回收类元数据,依赖 GC 和 ClassLoader 回收 | 需同时满足实例、Class 对象、ClassLoader 回收 |
常见异常
NoClassDefFoundError:- 类在编译期存在,但运行时找不到(如类文件被删除,或类加载器无法加载)。
- 这通常发生在类路径配置错误或部署问题导致 JVM 无法在运行时找到所需的类文件。
ClassNotFoundException:- 动态加载类时未找到(如
Class.forName()传入了错误的类名或类文件不存在)。 - 这通常是应用程序尝试通过名称加载一个不存在的类时抛出的。
- 动态加载类时未找到(如