★ 类加载过程分几步?双亲委派模型如何打破?

结论先行:类从字节码到可执行要经历加载 → 验证 → 准备 → 解析 → 初始化五个阶段(后四步合称“连接”)。默认的双亲委派模型让类加载请求“先问父加载器”,从而保证核心类不被篡改、同一类不重复加载;需要打破时通常靠重写 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)是“父加载器加载接口、却要调用子加载器的实现”,只能靠线程上下文类加载器打破委派。
笔记加载中…