Skip to content

02 - 类加载机制

定位: JVM如何将Class文件从静态字节流转化为运行时类对象,并管理其生命周期 面试高频度: ⭐⭐⭐⭐⭐

一、类加载在 JVM 体系中的位置

类加载在 JVM 启动→运行全流程中扮演桥梁角色:它接收 Class 文件的静态结构,将其转化为方法区的运行时数据结构,并在堆上生成 Class<?> 对象供反射使用。没有类加载,字节码只是一堆字节流。

  • 向上: 消费 Class 文件格式,解析常量池、字段表、方法表
  • 向下: 在方法区(元空间)构建类元数据,在堆上创建 Class 对象
  • 横向: 通过双亲委派模型维护 Java 类型体系的安全性

二、类加载生命周期时间线

JVM 启动 → 加载第一个类 (Bootstrap ClassLoader 加载核心类)


┌──────────────────────────────────────────────────────────────┐
│              类加载生命周期 (7 阶段交错执行)                    │
│                                                              │
│  ༌༌༌༌༌༌ Ⅰ  Ⅱ  Ⅲ  Ⅳ  Ⅴ  Ⅵ  Ⅶ                              │
│  ┌─────┐                                                      │
│  │ 加载 │═══════╗                                              │
│  └─────┘       ║                                              │
│  ┌───────┐     ║                                              │
│  │ 验证   │═════╬═══════════════════════════════════╗           │
│  └───────┘     ║                                   ║          │
│  ┌───────┐     ║                                   ║          │
│  │ 准备   │═════╝═══════════════════════════════════╝           │
│  └───────┘                                                    │
│  ┌───────┐                                                    │
│  │ 解析   │═══════╗     ← 可延迟到第一次使用 (迟到绑定)        │
│  └───────┘       ║                                            │
│  ┌─────────┐     ║                                            │
│  │ 初始化   │═════╝══════ 触发时机: new / getstatic / 反射等   │
│  └─────────┘                                                    │
│  ┌───────┐                                                      │
│  │ 使用   │══════════════════════════                            │
│  └───────┘                        ║                             │
│  ┌───────┐                        ║                             │
│  │ 卸载   │════════════════════════╝                             │
│  └───────┘                                                      │
└──────────────────────────────────────────────────────────────┘


垃圾回收可卸载 → 如果 ClassLoader 不可达

关键认知: 7 阶段不是严格的串行流水线。加载和验证可以并行(边读字节流边校验);准备分配内存但不赋初值;解析可以一直推迟到字节码指令第一次引用符号引用为止(迟到绑定);初始化只触发一次(JVM 保证线程安全)。

三、子专题导航

#主题面试频率核心内容
**类加载生命周期**类加载 7 阶段详解⭐⭐⭐⭐⭐加载→验证→准备→解析→初始化→使用→卸载,阶段交错动机,主动引用 vs 被动引用
**双亲委派模型与打破**双亲委派模型与打破⭐⭐⭐⭐⭐三层 ClassLoader 层级、向上委派的安全动机、SPI 打破委派、TCCL 桥接
**自定义类加载器实战**自定义类加载器实战⭐⭐⭐⭐findClass vs loadClass 职责分离、defineClass 原理、加密类加载、热部署
**Tomcat与Spring类加载分析**Tomcat 与 Spring 类加载分析⭐⭐⭐⭐Tomcat 多级 ClassLoader、Webapp 反向委派、Spring Boot fat jar 嵌套加载
**OSGI与SPI模块化**OSGI 与 SPI 模块化⭐⭐⭐OSGI Bundle 网状依赖、SPI ServiceLoader 原理、JDBC 驱动加载全链路、JPMS

四、核心考点速记

1. 双亲委派模型

ClassLoader.loadClass(name)


┌─ 检查类是否已加载 ──→ 已加载 → 直接返回

▼ (未加载)
┌─ 委托父 ClassLoader ──→ 父能加载 → 父返回
│                         父不能加载 → 自己尝试

┌─ findClass(name) → defineClass(byte[]) → 返回 Class

安全动机: java.lang.String 只能由 Bootstrap ClassLoader 加载。即使你写一个同名的 java.lang.String,双亲委派也会让 Bootstrap 的 String 优先——恶意代码根本无法替换核心类。

打破动机: SPI (JDBC、JNDI 等)。DriverManagerrt.jar(Bootstrap 加载),但 JDBC 驱动是应用 jar(AppClassLoader 加载)。Bootstrap 加载的代码需要调用应用类——委派方向反了。解决方案:线程上下文类加载器 (TCCL)

2. SPI 打破委派的全链路

DriverManager.getConnection(url)
    │ DriverManager 由 Bootstrap ClassLoader 加载 (rt.jar)

ServiceLoader.load(Driver.class)
    │ 用 TCCL (AppClassLoader) 加载驱动实现

读取 META-INF/services/java.sql.Driver
    │ (来自 mysql-connector-java.jar)

com.mysql.cj.jdbc.Driver
    │ TCCL 加载 → 触发 Driver 的静态块 → DriverManager.registerDriver()

返回可用 Driver 实现

3. Tomcat 类加载隔离

Bootstrap (JRE核心)

System (Tomcat启动)

Common (共享库: Servlet API, Tomcat自身类)
    ├── Catalina (Tomcat内部)
    └── Shared (所有Web应用共享)
            ├── Webapp1 (应用A: 先自己加载, 再委派)
            └── Webapp2 (应用B: 同名类不冲突)

为什么 WebappClassLoader 打破双亲委派: Web 应用需要能覆盖共享库中的同名校本。例如应用A用 Spring 4,应用B用 Spring 5——如果先委派给 Shared,两个应用永远用同一个版本。WebappClassLoader 先自己尝试加载,找不到再委派向上

五、面试高频追问一览

追问关联笔记频次
类加载有哪几个阶段?哪些可以并行?**类加载生命周期**极高
主动引用和被动引用的区别?**类加载生命周期**极高
双亲委派模型是什么?为什么要这样设计?**双亲委派模型与打破**极高
SPI 是如何打破双亲委派的?**OSGI与SPI模块化**
如何自定义一个 ClassLoader?**自定义类加载器实战**
Tomcat 的类加载机制?**Tomcat与Spring类加载分析**
为什么 static final 常量不需要触发类初始化?**类加载生命周期**
ClassNotFoundExceptionNoClassDefFoundError 的区别?**自定义类加载器实战**
Java 9 的模块化对类加载有什么影响?**OSGI与SPI模块化**中低

📚 相关文件

  • **Class文件结构** — Class 文件是类加载的输入
  • **方法区与元空间** — 类加载在方法区构建类元数据
  • **运行时常量池** — 符号引用→直接引用在运行时常量池中完成
  • **JVM文库设计文档**

Knowledge4J — Java 知识库