★ 类加载过程分几步?双亲委派模型如何打破?
结论先行:类从字节码到可执行要经历加载 → 验证 → 准备 → 解析 → 初始化五个阶段(后四步合称“连接”)。默认的双亲委派模型让类加载请求“先问父加载器”,从而保证核心类不被篡改、同一类不重复加载;需要打破时通常靠重写 loadClass 或使用线程上下文类加载器。
五个阶段要点
| 阶段 | 核心工作 | 关键细节 |
|---|---|---|
| 加载 | 读取字节码生成 Class 对象 | 来源可以是 jar、网络、动态生成 |
| 验证 | 校验文件格式与字节码语义 | 防止恶意或损坏字节码 |
| 准备 | 为静态变量分配内存并置零值 | 注意不是赋初始值,static final 常量除外 |
| 解析 | 符号引用替换为直接引用 | 可延迟到运行时首次使用 |
| 初始化 | 执行类构造器 <clinit> | 静态代码块与静态变量赋值在此完成 |
澄清:准备阶段给普通静态变量赋“零值”,只有 static final 编译期常量会在该阶段直接赋真实值。
双亲委派怎么工作
- 流程:类加载器收到请求后先委托父加载器,逐级上抛到 Bootstrap;父能加载则由父加载,否则才轮到自己尝试。
- 好处:java.lang.String 只能由 Bootstrap 加载,杜绝自定义同名类冒充核心类;同时避免同一条类在不同加载器下出现多份(类唯一性)。
- 打破方式:重写 loadClass 不走委派(如 Tomcat 隔离各 Web 应用);SPI 场景用线程上下文类加载器(如 JDBC DriverManager)。
- 委派方向:加载请求自下而上“问父”,加载能力自上而下“回退”,配合 Bootstrap → Platform → Application 层级保证类只被加载一次。
- 类唯一性:类是否相同由“全限定名 + 定义它的加载器”共同决定,双亲委派正是为了避免核心类出现多份。
记忆点:双亲委派 = “加载先问父,父不行自己上”;问“如何破坏”,答重写 loadClass 或线程上下文类加载器。
// 破坏双亲委派的经典姿势:覆写 loadClass 直接 findClass(示例勿仿)
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 跳过父加载器,先尝试自己加载
return findClass(name);
}
}
常见追问 / 记忆点
- 记忆点:初始化才执行静态代码;双亲委派本质是“加载请求向上抛、加载能力向下试”。
- 追问:验证与准备都发生在初始化之前,所以准备期的静态字段是零值而不是初始值。
- 追问:SPI(JDBC、JNDI)是“父加载器加载接口、却要调用子加载器的实现”,只能靠线程上下文类加载器打破委派。