1. 引言
在 JVM 相关的面试中,类加载过程几乎是必考的基础知识点。它不仅是理解 JVM 运行机制的前提,也是回答「双亲委派模型」「类加载器」「内存溢出排查」等问题的基础。很多候选人能背出「加载、验证、准备、解析、初始化」这五个阶段,但一旦被追问「每个阶段具体做了什么」「哪些阶段可以推迟」「什么时候触发初始化」,就容易卡壳。
本文以面试问答的形式,系统梳理类加载过程的完整链路、高频追问点以及易错点,帮助你既能在面试中答得准,也能答得深。
2. 类加载过程总览
类加载的完整生命周期包括以下七个阶段:
#mermaid-svg-bsLYrHudxgHntk8q{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bsLYrHudxgHntk8q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bsLYrHudxgHntk8q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bsLYrHudxgHntk8q .error-icon{fill:#552222;}#mermaid-svg-bsLYrHudxgHntk8q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bsLYrHudxgHntk8q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bsLYrHudxgHntk8q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bsLYrHudxgHntk8q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bsLYrHudxgHntk8q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bsLYrHudxgHntk8q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bsLYrHudxgHntk8q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bsLYrHudxgHntk8q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bsLYrHudxgHntk8q .marker.cross{stroke:#333333;}#mermaid-svg-bsLYrHudxgHntk8q svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bsLYrHudxgHntk8q p{margin:0;}#mermaid-svg-bsLYrHudxgHntk8q .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bsLYrHudxgHntk8q .cluster-label text{fill:#333;}#mermaid-svg-bsLYrHudxgHntk8q .cluster-label span{color:#333;}#mermaid-svg-bsLYrHudxgHntk8q .cluster-label span p{background-color:transparent;}#mermaid-svg-bsLYrHudxgHntk8q .label text,#mermaid-svg-bsLYrHudxgHntk8q span{fill:#333;color:#333;}#mermaid-svg-bsLYrHudxgHntk8q .node rect,#mermaid-svg-bsLYrHudxgHntk8q .node circle,#mermaid-svg-bsLYrHudxgHntk8q .node ellipse,#mermaid-svg-bsLYrHudxgHntk8q .node polygon,#mermaid-svg-bsLYrHudxgHntk8q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bsLYrHudxgHntk8q .rough-node .label text,#mermaid-svg-bsLYrHudxgHntk8q .node .label text,#mermaid-svg-bsLYrHudxgHntk8q .image-shape .label,#mermaid-svg-bsLYrHudxgHntk8q .icon-shape .label{text-anchor:middle;}#mermaid-svg-bsLYrHudxgHntk8q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bsLYrHudxgHntk8q .rough-node .label,#mermaid-svg-bsLYrHudxgHntk8q .node .label,#mermaid-svg-bsLYrHudxgHntk8q .image-shape .label,#mermaid-svg-bsLYrHudxgHntk8q .icon-shape .label{text-align:center;}#mermaid-svg-bsLYrHudxgHntk8q .node.clickable{cursor:pointer;}#mermaid-svg-bsLYrHudxgHntk8q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bsLYrHudxgHntk8q .arrowheadPath{fill:#333333;}#mermaid-svg-bsLYrHudxgHntk8q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bsLYrHudxgHntk8q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bsLYrHudxgHntk8q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bsLYrHudxgHntk8q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bsLYrHudxgHntk8q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bsLYrHudxgHntk8q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bsLYrHudxgHntk8q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bsLYrHudxgHntk8q .cluster text{fill:#333;}#mermaid-svg-bsLYrHudxgHntk8q .cluster span{color:#333;}#mermaid-svg-bsLYrHudxgHntk8q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bsLYrHudxgHntk8q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bsLYrHudxgHntk8q rect.text{fill:none;stroke-width:0;}#mermaid-svg-bsLYrHudxgHntk8q .icon-shape,#mermaid-svg-bsLYrHudxgHntk8q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bsLYrHudxgHntk8q .icon-shape p,#mermaid-svg-bsLYrHudxgHntk8q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bsLYrHudxgHntk8q .icon-shape rect,#mermaid-svg-bsLYrHudxgHntk8q .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bsLYrHudxgHntk8q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bsLYrHudxgHntk8q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bsLYrHudxgHntk8q :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 加载 Loading
验证 Verification
准备 Preparation
解析 Resolution
初始化 Initialization
使用 Using
卸载 Unloading
其中,加载、验证、准备、解析、初始化 这五个阶段属于「类加载过程」的核心,也是面试中常被考察的部分。需要特别注意的是,这五个阶段的开始顺序 是固定的,但不一定按顺序完成------解析阶段在某些情况下可以在初始化阶段之后才开始,这是为了支持 Java 的动态绑定特性。
3. 第一阶段:加载(Loading)
3.1 加载阶段做了什么
加载阶段由类加载器完成,主要做三件事:
- 通过类的全限定名获取定义此类的二进制字节流;
- 将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
- 在内存中生成一个代表该类的
java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
3.2 高频追问:二进制字节流只能从 Class 文件获取吗?
不是。二进制字节流可以来自:
- 本地磁盘的
.class文件; - JAR、WAR、EAR 等压缩包;
- 网络(如 Applet 的远程加载);
- 动态代理生成的字节码;
- JSP 生成的 Class 文件;
- 数据库中的字节流。
3.3 高频追问:数组类需要加载吗?
数组类本身不通过类加载器创建,而是由 JVM 直接在内存中动态构造。但数组的元素类型(如 String[] 中的 String)仍然需要由类加载器加载。
4. 第二阶段:验证(Verification)
验证阶段是连接阶段的第一步,目的是确保 Class 文件的字节流符合 JVM 规范,并且不会危害虚拟机自身的安全。
验证主要包含四个动作:
- 文件格式验证 :检查字节流是否符合 Class 文件格式规范,如魔数是否为
0xCAFEBABE、主次版本号是否在当前虚拟机可接受范围内。 - 元数据验证:对字节码描述的信息进行语义分析,如该类是否有父类、是否继承了被 final 修饰的类、是否实现了所有抽象方法。
- 字节码验证:通过数据流和控制流分析,确定程序语义是否合法、符合逻辑,这是整个验证过程中最复杂的一个环节。
- 符号引用验证:发生在解析阶段,确保符号引用能正确解析为直接引用。
高频追问:验证阶段可以关闭吗?
可以。JVM 提供了 -Xverify:none 参数来关闭大部分验证措施,以缩短类加载时间。但在生产环境中,出于安全考虑,不建议关闭。
5. 第三阶段:准备(Preparation)
准备阶段为类的静态变量 分配内存,并设置默认零值。
这里有一个高频易错点:准备阶段分配的是零值,而不是代码中赋的初始值。
例如:
java
public static int value = 123;
在准备阶段,value 的值为 0,而不是 123。真正把 value 赋值为 123 的 putstatic 指令,是在初始化阶段才执行的。
特殊情况:常量
如果静态变量被 final 修饰,并且在编译期就能确定值,那么它在准备阶段就会被赋上真实值。例如:
java
public static final int CONSTANT = 123;
CONSTANT 在准备阶段就直接被赋值为 123,因为 final 常量在编译期会被写入常量池的 ConstantValue 属性中。
6. 第四阶段:解析(Resolution)
解析阶段将常量池内的符号引用 替换为直接引用。
- 符号引用:用一组符号来描述所引用的目标,与内存布局无关,如类的全限定名、字段名、方法名。
- 直接引用:直接指向目标的指针、相对偏移量或一个能间接定位到目标的句柄,与内存布局相关。
解析动作主要针对以下七类符号引用:
- 类或接口
- 字段
- 类方法
- 接口方法
- 方法类型
- 方法句柄
- 调用点限定符
高频追问:解析一定在初始化之前吗?
不一定。JVM 规范允许在初始化阶段之后再解析某些符号引用,这是为了支持 Java 的动态绑定 特性。例如,对于 invokedynamic 指令,其解析往往发生在运行时。
7. 第五阶段:初始化(Initialization)
初始化阶段是类加载过程的最后一步 ,也是真正开始执行类中定义的 Java 程序代码的阶段。在这个阶段,JVM 会执行 <clinit>() 方法。
<clinit>() 方法由编译器自动收集类中所有静态变量的赋值动作 和静态代码块中的语句合并生成。
7.1 什么情况下会触发初始化
以下六种情况属于「主动引用」,会触发类的初始化:
- 遇到
new、getstatic、putstatic、invokestatic字节码指令时; - 使用
java.lang.reflect包的方法对类进行反射调用时; - 初始化一个类时,如果其父类还未初始化,则先触发父类初始化;
- 虚拟机启动时,初始化包含
main()方法的主类; - 使用 JDK 7 的动态语言支持时,
java.lang.invoke.MethodHandle解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄时; - 接口中定义了 JDK 8 的默认方法时,如果该接口的实现类被初始化,接口要在其之前被初始化。
7.2 什么情况不会触发初始化(被动引用)
以下情况属于「被动引用」,不会触发初始化:
- 通过子类引用父类的静态字段,不会导致子类初始化;
- 通过数组定义来引用类,不会触发该类的初始化;
- 访问常量(编译期常量),不会触发定义该常量的类的初始化。
7.3 高频追问:<clinit>() 方法有哪些特点
- 由编译器自动收集生成,不需要程序员显式编写;
- 父类的
<clinit>()先于子类的执行; - 对于接口,只有定义了默认方法时,父接口的
<clinit>()才会先于子接口执行; <clinit>()方法对于类或接口来说并不是必需的,如果没有静态变量赋值和静态代码块,就不会生成;- 虚拟机会保证一个类的
<clinit>()在多线程环境中被正确加锁同步。
8. 面试高频代码题
题目一:子类引用父类静态字段
java
public class Parent {
static {
System.out.println("Parent static block");
}
public static int value = 123;
}
public class Child extends Parent {
static {
System.out.println("Child static block");
}
}
public class Test {
public static void main(String[] args) {
System.out.println(Child.value);
}
}
输出结果:
Parent static block
123
解析: 通过子类引用父类的静态字段,属于被动引用,不会触发子类的初始化,只会触发父类的初始化。
题目二:数组定义引用类
java
public class ArrayTest {
public static void main(String[] args) {
SuperClass[] arr = new SuperClass[10];
System.out.println(arr.length);
}
}
输出结果:
10
解析: 通过数组定义来引用类,不会触发该类的初始化。数组类由 JVM 直接创建,元素类型 SuperClass 不会被初始化。
题目三:常量不会触发初始化
java
public class ConstClass {
static {
System.out.println("ConstClass static block");
}
public static final String HELLO = "hello";
}
public class ConstTest {
public static void main(String[] args) {
System.out.println(ConstClass.HELLO);
}
}
输出结果:
hello
解析: HELLO 是编译期常量,在编译阶段就被写入 ConstTest 的常量池中,对 ConstClass.HELLO 的引用在编译期就被替换为常量值,因此不会触发 ConstClass 的初始化。
9. 类加载过程与双亲委派模型的关系
类加载过程中的「加载」阶段,需要类加载器来完成。JVM 默认采用双亲委派模型:
#mermaid-svg-gObAUkwg7pfbIkwl{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gObAUkwg7pfbIkwl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gObAUkwg7pfbIkwl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gObAUkwg7pfbIkwl .error-icon{fill:#552222;}#mermaid-svg-gObAUkwg7pfbIkwl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gObAUkwg7pfbIkwl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gObAUkwg7pfbIkwl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gObAUkwg7pfbIkwl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gObAUkwg7pfbIkwl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gObAUkwg7pfbIkwl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gObAUkwg7pfbIkwl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gObAUkwg7pfbIkwl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gObAUkwg7pfbIkwl .marker.cross{stroke:#333333;}#mermaid-svg-gObAUkwg7pfbIkwl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gObAUkwg7pfbIkwl p{margin:0;}#mermaid-svg-gObAUkwg7pfbIkwl .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gObAUkwg7pfbIkwl .cluster-label text{fill:#333;}#mermaid-svg-gObAUkwg7pfbIkwl .cluster-label span{color:#333;}#mermaid-svg-gObAUkwg7pfbIkwl .cluster-label span p{background-color:transparent;}#mermaid-svg-gObAUkwg7pfbIkwl .label text,#mermaid-svg-gObAUkwg7pfbIkwl span{fill:#333;color:#333;}#mermaid-svg-gObAUkwg7pfbIkwl .node rect,#mermaid-svg-gObAUkwg7pfbIkwl .node circle,#mermaid-svg-gObAUkwg7pfbIkwl .node ellipse,#mermaid-svg-gObAUkwg7pfbIkwl .node polygon,#mermaid-svg-gObAUkwg7pfbIkwl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gObAUkwg7pfbIkwl .rough-node .label text,#mermaid-svg-gObAUkwg7pfbIkwl .node .label text,#mermaid-svg-gObAUkwg7pfbIkwl .image-shape .label,#mermaid-svg-gObAUkwg7pfbIkwl .icon-shape .label{text-anchor:middle;}#mermaid-svg-gObAUkwg7pfbIkwl .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gObAUkwg7pfbIkwl .rough-node .label,#mermaid-svg-gObAUkwg7pfbIkwl .node .label,#mermaid-svg-gObAUkwg7pfbIkwl .image-shape .label,#mermaid-svg-gObAUkwg7pfbIkwl .icon-shape .label{text-align:center;}#mermaid-svg-gObAUkwg7pfbIkwl .node.clickable{cursor:pointer;}#mermaid-svg-gObAUkwg7pfbIkwl .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gObAUkwg7pfbIkwl .arrowheadPath{fill:#333333;}#mermaid-svg-gObAUkwg7pfbIkwl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gObAUkwg7pfbIkwl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gObAUkwg7pfbIkwl .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gObAUkwg7pfbIkwl .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gObAUkwg7pfbIkwl .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gObAUkwg7pfbIkwl .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gObAUkwg7pfbIkwl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gObAUkwg7pfbIkwl .cluster text{fill:#333;}#mermaid-svg-gObAUkwg7pfbIkwl .cluster span{color:#333;}#mermaid-svg-gObAUkwg7pfbIkwl div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gObAUkwg7pfbIkwl .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gObAUkwg7pfbIkwl rect.text{fill:none;stroke-width:0;}#mermaid-svg-gObAUkwg7pfbIkwl .icon-shape,#mermaid-svg-gObAUkwg7pfbIkwl .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gObAUkwg7pfbIkwl .icon-shape p,#mermaid-svg-gObAUkwg7pfbIkwl .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gObAUkwg7pfbIkwl .icon-shape rect,#mermaid-svg-gObAUkwg7pfbIkwl .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gObAUkwg7pfbIkwl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gObAUkwg7pfbIkwl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gObAUkwg7pfbIkwl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 启动类加载器 Bootstrap ClassLoader
扩展类加载器 Extension ClassLoader
应用程序类加载器 Application ClassLoader
自定义类加载器 User ClassLoader
双亲委派模型的工作过程是:如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中。只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。
这样做的好处是:保证 Java 核心类库的类型安全,避免核心 API 被随意替换。
10. 总结与面试答题建议
类加载过程是 JVM 面试中的高频考点,建议按以下思路组织答案:
- 先总述:类加载过程包含加载、验证、准备、解析、初始化五个阶段;
- 再分述:每个阶段的核心职责各用一两句话说明;
- 重点展开:准备阶段的零值问题、初始化阶段的触发条件、被动引用场景;
- 结合代码:用上面的代码题来佐证你的理解;
- 适当延伸:提到双亲委派模型,展示知识体系的完整性。
掌握类加载过程,不仅是为了应付面试,更是理解 JVM 内存模型、类加载器、热部署、SPI 机制等进阶知识的基础。建议在理解的基础上,亲手运行上面的代码题,加深印象。