适用读者:被"双亲委派""打破双亲委派"绕晕的人。
前置:S04-J03(类文件结构)。
实测环境:OpenJDK 21.0.12.1(类加载器链实测,2026-11-05)。
覆盖旧文:A-1129(515) A-2588(404) A-2583(986) A-2586(587) A-2589(380) + 1009(86) 1015(75) 1031(80) 987(71) 986(65) 1620(126) 1643(301) 1618(189) 1029(83) = 3,948 点。
覆盖扩展:E-S05-013(双亲委派与破坏)、E-S05-014(执行引擎)。
上一篇:S05-J04 · JVM 运行时数据区与垃圾回收基础
下一篇:S05-A01 · GC 收集器选型与调优。
1. 一句话结论
类加载分五步(加载→验证→准备→解析→初始化);双亲委派的意义是"保证核心类不被自定义类覆盖";它被"破坏"的三种方式都是为了插件化/隔离。
2. 实测:类加载器链
ClassLoader cl = Cl.class.getClassLoader();
while (cl != null) { print(cl); cl = cl.getParent(); }
实测输出:
jdk.internal.loader.ClassLoaders$AppClassLoader@639fee48
jdk.internal.loader.ClassLoaders$PlatformClassLoader@2a139a55
String 类加载器: null(null = Bootstrap)
| 加载器 | 负责 |
|---|---|
| Bootstrap(null) | java.* 核心类(C++ 实现,Java 里显示 null) |
| Platform(原 Extension) | JDK 平台模块 |
| App(原 System) | classpath 上的应用类 |
| 自定义 | 插件、热部署 |
String.class.getClassLoader() == null 就是"核心类由 Bootstrap 加载"的直接证据。
3. 双亲委派与它的三个"破坏"(E-S05-013)
流程:收到加载请求 → 先问父加载器 → 父加载不了才自己加载。
| 破坏方式 | 场景 |
|---|---|
重写 loadClass |
热部署(如某些框架每次重新加载) |
| 线程上下文类加载器(TCCL) | SPI/JDBC:接口在 Bootstrap,实现在应用 classpath |
| OSGi/模块化 | 网状委派而非树状 |
TCCL 的意义:ServiceLoader 用 Thread.currentThread().getContextClassLoader()——因为"接口由父加载器加载、实现由子加载器加载",父看不到子,只能靠线程上下文反向查找(与 S04-J05 的 SPI 呼应)。
4. 五个阶段与"初始化时机"
| 阶段 | 做什么 |
|---|---|
| 加载 | 读字节码 → 生成 Class 对象 |
| 验证 | 格式/语义/字节码校验 |
| 准备 | 静态变量分配内存并赋零值(不是初始值!) |
| 解析 | 符号引用 → 直接引用 |
| 初始化 | 执行 <clinit>(静态块 + 静态变量赋值) |
"准备赋零值"解释了 S04-B04 的初始化顺序:静态变量先 0,再在 <clinit> 里赋真值。
触发初始化的六个时机(记忆点):new、访问静态字段/方法、反射、子类初始化、主类、MethodHandle。
5. 执行引擎(E-S05-014)
| 方式 | 说明 |
|---|---|
| 解释执行 | 逐条翻译字节码 |
| JIT 编译(C1/C2) | 热点代码编译成机器码 |
| 分层编译 | 先 C1 快速编译,再 C2 深度优化 |
| 去优化(Deopt) | 假设被打破 → 退回解释执行 |
这就是"Java 启动慢但跑久了快"的原因:预热期在收集profile,稳定后 C2 编译的代码性能接近 C++。
6. 五个误区
| 误区 | 真相 |
|---|---|
| "双亲委派是强制的" | 可被重写(三种破坏方式) |
| "准备阶段就赋初始值" | 赋零值,初始值在 <clinit> |
| "Bootstrap 加载器能拿到" | Java 里是 null |
| "类加载一次就永远不变" | 可卸载(需类加载器可回收) |
| "JIT 编译后不会回退" | 可去优化 |
7. 自检
- 五个阶段分别做什么?"准备"赋什么值?
- 双亲委派解决什么问题?
- 三种"破坏"分别为了什么场景?
- TCCL 为什么必须存在(结合 SPI)?
- 为什么 Java "启动慢、跑久了快"?
8. 下一篇
S05-A01 · GC 收集器选型与调优
类加载讲完,进架构师层:CMS/G1/ZGC 怎么选,以及 GC 日志里哪个信号最该警惕。
本篇属于系列《S05 · Java 并发与 JVM》。旧文来源:A-1129/2588/2583/2586/2589/1009/1015/1031/987/986/1620/1643/1618/1029;扩展点 E-S05-013/014。类加载器链为 OpenJDK 21 实测。