第二章:类加载机制与类生命周期

2 阅读5分钟

第二章:类加载机制与类生命周期 —— 双亲委派模型到底解决了什么问题?

本章目标:掌握 JVM 类加载器体系、双亲委派模型、类加载生命周期,以及类初始化与卸载原理,为理解内存管理和类加载优化打基础。


一、为什么要讲类加载机制?

Java 是动态语言的一大特点:

  • 类在 运行时 才会被加载
  • JVM 可以根据需要延迟加载类
  • 动态加载使 Java 支持 跨平台、插件化、热部署

例如:

Class<?> clazz = Class.forName("com.example.User");
Object obj = clazz.newInstance();

这段代码可以在运行时根据类名加载 User 类,而不是在编译时硬编码。

如果不理解类加载机制,你很难理解:

  • JVM 内存分区
  • 静态代码块执行时机
  • 自定义类加载器原理
  • ClassCastException 和类卸载问题

二、类加载器(ClassLoader)

JVM 的类加载是由 ClassLoader 完成的。

核心概念:

名称作用
Bootstrap ClassLoader启动类加载器,负责加载 JDK 核心类库(rt.jar
Extension ClassLoader扩展类加载器,加载 JDK 扩展库(jre/lib/ext
AppClassLoader应用类加载器,加载classpath下的用户类
自定义 ClassLoader用户自定义,可实现热加载、隔离等

执行流程示意图:

         AppClassLoader
                │
       ------------------
       │                │
ExtensionClassLoader    自定义ClassLoader
       │
BootstrapClassLoader

1. 双亲委派模型

定义

当类加载器收到类加载请求时,它 先把请求交给父加载器,父加载器加载不了再自己尝试加载。

流程:

ClassLoader.loadClass("com.example.User")
       │
       ▼
父加载器
       │
       ▼
尝试加载
       │
       └─> 成功?返回
       └─> 失败?子加载器加载

为什么要双亲委派?

  1. 保证核心类安全
    避免用户自定义类覆盖 java.lang.Stringjava.lang.Object
  2. 避免重复加载
    如果每个 ClassLoader 都可以独立加载 String,可能产生多个 java.lang.String 类对象。
  3. 提高效率
    系统类先由父加载器加载,避免重复查找。

2. 类加载器示例

public class ClassLoaderDemo {
    public static void main(String[] args) throws Exception {
        ClassLoader appClassLoader = ClassLoaderDemo.class.getClassLoader();
        System.out.println("AppClassLoader: " + appClassLoader);

        ClassLoader extClassLoader = appClassLoader.getParent();
        System.out.println("ExtClassLoader: " + extClassLoader);

        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println("BootstrapClassLoader: " + bootstrapClassLoader);
    }
}

输出示例:

AppClassLoader: sun.misc.Launcher$AppClassLoader@18b4aac2
ExtClassLoader: sun.misc.Launcher$ExtClassLoader@1b6d3586
BootstrapClassLoader: null

注意:BootstrapClassLoaderC++实现的,所以输出为 null


三、类加载生命周期

JVM 中一个类从 加载到卸载 的完整生命周期:

加载(Loading)
验证(Verification)
准备(Preparation)
解析(Resolution)
初始化(Initialization)
使用(Using)
卸载(Unloading)

1. 加载(Loading)

  • 读取 .class 文件
  • 将二进制数据放入 方法区
  • 生成 Class 对象(元数据)
Class<?> clazz = Class.forName("com.example.User");

此时触发 加载阶段


2. 验证(Verification)

  • 确保字节码合法
  • 不会破坏 JVM 安全
  • 验证类型、语法、堆栈安全

3. 准备(Preparation)

  • 为类的 静态变量 分配内存
  • 并设置 默认值
public class User {
    static int count = 10;
    static String name = "Tom";
}
  • count → 0
  • name → null

注意:赋值为常量的静态变量可能会直接在准备阶段赋值。


4. 解析(Resolution)

  • 将符号引用转换为直接引用
  • 例如:方法调用、类字段引用都被解析

5. 初始化(Initialization)

  • 执行类的 静态代码块 和静态变量赋值
public class User {
    static int count = 10;
    static {
        System.out.println("User类初始化");
        count = 20;
    }
}

输出:

User类初始化
  • 静态变量最终值为 20

初始化触发条件

  • new 创建对象
  • 调用静态方法
  • 访问静态字段(非编译时常量)
  • Class.forName() 显式初始化

6. 使用(Using)

类被加载、初始化完成后,程序可以正常使用。

User user = new User();
System.out.println(User.count); // 20

7. 卸载(Unloading)

  • ClassLoader 不再引用类,类及其实例可被 GC 回收
  • 一般 自定义 ClassLoader 热加载时会用到
  • JVM 内置类不会被卸载

四、双亲委派模型解决了什么问题?

总结如下:

问题双亲委派解决方案
核心类被覆盖父类优先加载,保证系统安全
类重复加载父加载器先加载,子加载器不重复加载
系统安全防止恶意用户定义 java.lang.*

五、类加载优化技巧(拓展)

  1. 自定义 ClassLoader
    可以动态加载模块、插件
URLClassLoader loader = new URLClassLoader(urls);
Class<?> plugin = loader.loadClass("com.example.Plugin");
  1. 避免重复加载类
    尽量使用父类加载器加载系统类库
  2. 延迟加载
    用到类时才加载,减少启动时间

六、面试高频问题

  1. 双亲委派模型是什么?为什么要用?
    答:父加载器优先加载,保证核心类安全和类唯一性。
  2. 类加载的五个阶段是什么?
    答:加载 → 验证 → 准备 → 解析 → 初始化
  3. 什么时候类会被初始化?
    答:new、调用静态方法、访问非编译期常量、Class.forName()
  4. BootstrapClassLoader 为什么显示 null?
    答:它是 JVM 本身实现的,不是 Java 对象。
  5. 类什么时候会卸载?
    答:ClassLoader 不再引用该类,且类实例全部回收时。

七、本章总结

  • 类加载器:负责加载类,支持跨平台和热部署
  • 双亲委派模型:父先行加载,保证核心类安全
  • 类生命周期:加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载
  • 初始化触发条件new / 静态方法 / 非编译期常量 / Class.forName()
  • 卸载与自定义 ClassLoader:用于插件化、热加载

下一章将深入 Java 内存模型 (JMM) 与运行时数据区,讲解:

  • 堆、栈、方法区、程序计数器、线程共享/独占内存
  • 线程安全与可见性原理
  • 与类加载器和 GC 的关系

如果你愿意,我可以直接帮你写 第三章:Java 内存模型 (JMM) 与运行时数据区,把每个内存区、线程模型和可见性讲透。

你希望我直接写吗?