JVM类加载器中双亲委派模型的工作模式

总结摘要
JVM类加载器中双亲委派模型的工作模式

双亲委派模型是 JVM 类加载器的工作模式。要理解它,可以先想一个问题:当你写下 java.lang.String 时,怎么保证你加载的 String 是 JDK 的核心类,而不是你自己写的、可能包含恶意代码的 String?

双亲委派就是用来解决这个安全问题避免重复加载的机制。

1. 核心概念:什么是双亲委派?

当一个类加载器收到类加载的请求时,它不会自己先去尝试加载这个类,而是把请求委派给父加载器去完成。每一个层级的加载器都是如此。只有当父加载器在自己的加载范围内找不到这个类时,子加载器才会自己尝试去加载。

这个过程可以总结为一句话:向上委托检查,向下自行加载


2. 标准的类加载器层级

JVM 在运行时,有三层核心的类加载器:

  1. 启动类加载器(Bootstrap ClassLoader)

    • 最顶层的加载器,由 C++ 实现(在 HotSpot 虚拟机中)。
    • 负责加载 JAVA_HOME/jre/lib 目录下的核心类库(如 rt.jar 中的 java.lang.*)。
    • 它是所有其他类加载器的“祖父”。
  2. 扩展类加载器(Extension ClassLoader)

    • 负责加载 JAVA_HOME/jre/lib/ext 目录下的类库。
    • 它的父加载器是 Bootstrap ClassLoader。
  3. 应用程序类加载器(Application ClassLoader)

    • 我们最常接触的加载器。负责加载 Classpath(环境变量)下的类,也就是我们写的代码和引用的第三方 JAR 包。
    • 它的父加载器是 Extension ClassLoader。

3. 工作流程演示

假设 JVM 需要加载 com.example.MyClass 和你代码中使用的 java.lang.String

场景一:加载核心类 java.lang.String

  1. 开始请求Application ClassLoader 收到加载 java.lang.String 的请求。
  2. 向上委托:它先不加载,而是问它的父加载器 Extension ClassLoader:“爸,你加载这个类吗?”
  3. 继续向上Extension ClassLoader 也不加载,继续问它的父加载器 Bootstrap ClassLoader:“爷爷,你加载这个类吗?”
  4. 顶层加载Bootstrap ClassLoader 一看,java.lang.String 正是它负责的(在 rt.jar 里),于是它加载了这个类。
  5. 返回结果:加载完成后,类信息沿着链条返回。java.lang.String 最终由最顶层的 Bootstrap ClassLoader 加载。

场景二:加载项目中的类 com.example.MyClass

  1. 开始请求Application ClassLoader 收到加载 com.example.MyClass 的请求。
  2. 向上委托Application ClassLoader 问父加载器 Extension ClassLoader
  3. 继续向上Extension ClassLoader 问父加载器 Bootstrap ClassLoader
  4. 查找失败Bootstrap ClassLoaderrt.jar 里找不到 com.example.MyClass,返回“没找到”。
  5. 继续查找Extension ClassLoaderlib/ext 目录下也找不到,也返回“没找到”。
  6. 自行加载:既然父加载器们都找不到,Application ClassLoader 就在项目的 Classpath 中找到了 MyClass,并加载它。

4. 为什么这样设计?(好处)

  1. 沙箱安全(避免核心API被篡改)

    • 假设你自己写了一个名为 java.lang.String 的类,里面包含恶意代码。
    • 根据双亲委派模型,加载 java.lang.String 时,会一直向上委托到 Bootstrap ClassLoader
    • Bootstrap ClassLoader 发现 rt.jar 里已经有标准的 String 了,就会加载标准的 String,而不会去加载你写的那个。
    • 这就保证了 Java 核心类库的安全。
  2. 避免类的重复加载

    • 当父加载器已经加载过某个类时,子加载器就不需要再加载一次。这保证了同一个类在 JVM 中只会有一份内存数据。

5. 一个容易混淆的点

  • 这里的“双亲”并不是继承关系(extends),而是组合关系(Composition)
  • 类加载器中有一个属性叫 parent,用来指向它的父加载器。所以更准确的叫法应该是“父委托模型”。

6. 有没有破坏这个模型的情况?

有。常见的破坏场景包括:

  • JDBC / JNDI 等 SPI(服务提供接口)
    • 问题:这些是 Java 核心 API (java.sql.DriverManagerBootstrap ClassLoader 加载),但实现类(如 MySQL 驱动)却在 Classpath 中(应由 Application ClassLoader 加载)。
    • 解决:核心 API 无法加载具体实现。因此引入了 线程上下文类加载器(Thread Context ClassLoader),让核心 API 能“借用”应用程序类加载器去加载所需的实现类。这可以看作是一种对双亲委派模型的“逆向使用”。