Java 类的生命周期是指从 加载(Loading) 到 卸载(Unloading) 的整个过程,共分为 7 个阶段。JVM 通过 类加载器(ClassLoader) 和 运行时数据区(Runtime Data Areas) 管理类的生命周期。
1. 类生命周期的 7 个阶段
(1) 加载(Loading)
-
任务:查找并加载类的二进制字节码(
.class
文件)。 -
触发条件:
-
new
实例化对象。 -
访问类的静态字段或方法(
static
)。 -
反射调用(
Class.forName()
)。
-
-
加载来源:
-
本地文件系统(
ClassPath
)。 -
JAR 包、网络、动态代理生成等。
-
-
关键点:
-
由 类加载器(ClassLoader) 完成。
-
生成
Class
对象(存储在 方法区)。
-
(2) 验证(Verification)
任务:确保
.class
文件符合 JVM 规范,防止恶意代码。检查内容:
文件格式(魔数
0xCAFEBABE
)。元数据(如是否继承
final
类)。字节码合法性(无非法跳转)。
符号引用验证(如能否找到引用的类)。
(3) 准备(Preparation)
-
任务:为 静态变量 分配内存并设置初始值(零值)。
-
示例:
static int a = 10; // 准备阶段 a = 0(初始化阶段才赋值为 10) static final int b = 20; // final 修饰的常量直接赋值为 20
(4) 解析(Resolution)
任务:将符号引用(常量池中的
CONSTANT_Class
、CONSTANT_Methodref
等)转换为直接引用(内存地址)。解析目标:
类/接口、字段、方法、方法类型等。
(5) 初始化(Initialization)
任务:执行类构造器
<clinit>()
,初始化静态变量和静态代码块。触发条件(首次主动使用时):
new
、getstatic
、putstatic
、invokestatic
指令。反射调用。
子类初始化时,父类需先初始化。
线程安全:JVM 保证
<clinit>()
只执行一次(加锁)。
(6) 使用(Using)
任务:类完全加载后,可被程序正常使用(创建对象、调用方法等)。
运行时数据区:
堆:存储对象实例。
方法区:存储类信息、静态变量等。
(7) 卸载(Unloading)
任务:从 JVM 中移除类的
Class
对象和字节码。触发条件:
类的所有实例已被 GC。
加载该类的
ClassLoader
已被 GC。该类对应的
Class
对象无其他地方引用。应用场景:
动态加载(如 OSGi、热部署)。
2. 类加载器(ClassLoader)
(1) 双亲委派模型
-
BootstrapClassLoader:加载
JAVA_HOME/lib
下的核心类(如java.lang.*
)。 -
ExtClassLoader:加载
JAVA_HOME/lib/ext
下的扩展类。 -
AppClassLoader:加载
ClassPath
下的用户类。 -
自定义 ClassLoader:可重写
findClass()
实现特殊加载逻辑。
(2) 打破双亲委派
场景:
SPI 机制(如 JDBC 驱动加载)。
热部署(如 Tomcat 的 WebAppClassLoader)。
方法:重写
loadClass()
逻辑。
3. 常见面试问题
3.1: 类初始化顺序?
父类静态变量 → 父类静态块 → 子类静态变量 → 子类静态块 →
父类实例变量 → 父类构造块 → 父类构造函数 →
子类实例变量 → 子类构造块 → 子类构造函数
3.2: 什么时候会触发类初始化?
访问
static
字段或方法(除final
常量)。反射调用
Class.forName()
。子类初始化时父类未初始化。
3.3: <clinit>()
和 <init>()
的区别?
<clinit>() | <init>() |
---|---|
类构造器(静态初始化) | 实例构造器(对象初始化) |
由 JVM 调用,线程安全 | 由 new 触发 |
只执行一次 | 每次 new 都会执行 |