# 46. JVM 的类加载过程是什么?有哪些关键步骤?
# 标准答案
JVM 的类加载过程分为 加载(Loading)、链接(Linking)、初始化(Initialization) 三个主要阶段。链接阶段又包含 验证(Verification)、准备(Preparation)、解析(Resolution)。整个过程确保类在运行时被正确加载、验证并准备使用。
# 答案解析
Java 采用 按需加载(Lazy Loading) 机制,只有在首次使用某个类时,JVM 才会触发类加载。类的生命周期包括:加载 -> 链接(验证、准备、解析)-> 初始化 -> 使用 -> 卸载。
# 1. 加载(Loading)
- 从字节码(Class 文件)获取类定义,可能来源于 JAR 包、文件系统、网络等。
- 生成
Class
对象,并存入 方法区(Metaspace),在堆中创建java.lang.Class
实例进行管理。 - 类加载器(ClassLoader)负责查找、转换和定义类,使用 双亲委派模型(Bootstrap -> Ext -> App -> Custom)。
# 2. 链接(Linking)
包含以下三个步骤:
- 验证(Verification):检查 Class 文件格式是否符合 JVM 规范,防止恶意字节码攻击。
- 准备(Preparation):为类的静态变量分配 默认初始值(非
final
变量),但不会执行静态代码块。 - 解析(Resolution):将 符号引用(Symbolic Reference)转换为直接引用,如解析
MethodHandle
、字段和方法调用。
# 3. 初始化(Initialization)
- 执行静态变量赋值和静态代码块,按代码顺序执行。
- 父类优先初始化,子类初始化前必须保证父类已完成初始化。
- 只有在满足以下条件时才会触发类初始化:
- 首次主动使用该类(如
new
、访问静态字段、调用静态方法)。 - 通过反射调用
Class.forName("XXX")
。 - 子类初始化时,父类必须先初始化。
- Java 入口
main(String[] args)
所在的类。
- 首次主动使用该类(如
# 代码示例
class Parent {
static int A = 10;
static { System.out.println("Parent static block"); }
}
class Child extends Parent {
static int B = 20;
static { System.out.println("Child static block"); }
}
public class Test {
public static void main(String[] args) {
System.out.println(Child.B);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
输出顺序:
Parent static block
Child static block
20
1
2
3
2
3
解析:
- 访问
Child.B
时,JVM 先初始化Parent
,然后初始化Child
。 - 静态代码块在类初始化阶段执行,
Parent
先执行,Child
后执行。
# 常见错误
- 误以为
Class.forName("X")
只是加载类,实际上它会触发类的 初始化(可用ClassLoader.loadClass("X")
仅加载但不初始化)。 - 误解
static final
变量的初始化:不会触发 A 的初始化,因为class A { static final int X = 100; static { System.out.println("Class A initialized"); } } public class Test { public static void main(String[] args) { System.out.println(A.X); } }
1
2
3
4
5
6
7
8
9static final
变量在编译期就已存入常量池,不依赖类初始化。
# 最佳实践
- 避免
Class.forName()
触发不必要的类初始化。 - 使用
ClassLoader.loadClass()
仅加载但不初始化类,提高性能。 - 使用
-XX:+TraceClassLoading
监控类加载情况。
# 深入追问
双亲委派机制的作用是什么?如何破坏它?
- 作用:防止类被重复加载,保证 Java 核心 API 的安全性。
- 破坏方式:自定义
ClassLoader
,重写findClass()
方法绕过super.loadClass()
。
为什么
static final
变量不会触发类初始化?- 编译期常量优化,
static final
变量值会直接存入常量池,无需类加载。
- 编译期常量优化,
JVM 何时卸载类?
- 当
ClassLoader
没有被任何对象引用,且其加载的所有类都不可达时,JVM 会卸载该类,释放 Metaspace 空间。
- 当
# 相关面试题
- 类加载器(ClassLoader)如何工作?如何自定义类加载器?
- 什么是双亲委派模型?如何破坏它?
- 类加载过程中,哪些操作会触发类的初始化?