Skip to content

概念卡片:类加载机制与双亲委派

一句话机制

JVM 把 .class 的二进制字节流按「加载→验证→准备→解析→初始化」装入内存并生成 Class 对象,加载时按「双亲委派」自底向上请求、自顶向下加载,保证核心类唯一且安全。 类的静态初始化由 <clinit>() 完成,JVM 保证它多线程安全。

类生命周期 7 阶段

加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载
        └────── 连接(Linking) ──────┘
阶段做什么
加载通过全限定名找字节流 → 转成方法区运行时结构 → 生成 Class 对象
验证4 步:文件格式/元数据/字节码/符号引用,保证字节码合法
准备类变量(static)分配内存并设零值
解析常量池符号引用 → 直接引用
初始化执行 <clinit>(),真正执行 static 块 + 静态变量赋值
卸载Class 对象被 GC(需满足 3 条件)

不变量(准备 vs 初始化赋值时机)static int a = 1 在准备阶段是 0、初始化阶段才赋 1;但 static final int C = 10(编译期常量)在准备阶段就赋好值。区分点:是否需要执行代码/putstatic 指令——基本类型常量、String 字面量在准备阶段,引用类型(如 Integer.valueOf)、newRandom 在初始化阶段。

java
public static int a = 1;                    // 初始化阶段赋值
public static final int C = 10;             // 准备阶段赋值
public static final Integer I = Integer.valueOf(100); // 初始化阶段赋值
public static final String s = new String("x");       // 初始化阶段赋值

双亲委派模型

类加载请求先交给父加载器,父加载器加载不了才自己加载。 加载顺序自底向上(AppClassLoader → ExtClassLoader → BootstrapClassLoader),加载动作自顶向下。

加载器加载范围
BootstrapClassLoader核心库(rt.jar/lib,C++ 实现,无父)
ExtClassLoader扩展库(lib/ext
AppClassLoader应用 classpath

作用:① 避免类重复加载;② 防止核心 API 被篡改(自定义的 java.lang.String 永远轮不到自己被加载)。打破双亲委派靠重写 loadClass()(如 Tomcat 的 WebAppClassLoader 隔离各应用、JDBC 的 SPI 反向委派)。

<clinit>()<init>()

  • <clinit>()类的初始化方法,由 static 块 + 静态变量赋值合并自动生成,只出现一次。父类 <clinit> 先于子类执行。
  • <init>()对象的初始化方法(实例字段 + 构造方法),每次 new 都调用。
  • 无 static 赋值/静态块的类不生成 <clinit>()

⚠️ 隐蔽死锁<clinit>() 是线程安全的(多线程初始化同一类只有一个线程执行、其余阻塞)。若 A 类的 <clinit>Class.forName("B")、B 类的 <clinit>Class.forName("A"),两线程各持一锁互相等待 → 死锁,且没有可见的锁信息,极难排查

主动初始化(6 种触发)

  1. new / getstatic / putstatic / invokestatic 4 条字节码指令
  2. 反射调用(Class.forName()newInstance()
  3. 初始化子类先初始化父类
  4. 启动时初始化 main 方法所在类
  5. MethodHandle/VarHandlefindStaticVarHandle
  6. 接口 default 方法:实现类初始化前,接口先初始化

被动引用(不会初始化):访问父类静态字段、通过数组定义引用、访问编译期常量 static final

卸载 3 条件

类被卸载(Class 对象被 GC)需同时满足:① 该类的所有实例已 GC;② 类无任何地方被引用;③ 加载它的类加载器实例已 GC。因此 JDK 自带类加载器加载的类永不被卸载,只有自定义类加载器加载的类才可能被卸载(这是热部署/热加载的基础)。

常见误解(避坑)

  1. ❌ "类加载完就能用"。→ 加载只是第一步,还要经过连接(验证/准备/解析)和初始化才能真正用;Class.forName() 会触发初始化,而 ClassLoader.loadClass() 只加载不初始化(这是 JDBC 驱动延迟加载的原理)。
  2. ❌ "static final 常量都在初始化阶段赋值"。→ 编译期常量(基本类型/字面量 String)在准备阶段就赋好;带方法调用/new 的引用类型常量在初始化阶段。
  3. ❌ "类变量内存一定在方法区"。→ JDK7 起静态变量随 Class 对象移到堆中,方法区只留类型信息。
  4. ❌ "双亲委派不可打破"。→ 重写 loadClass() 即可打破(Tomcat/热部署/SPI 都在用)。

关联

最近更新