JVM类加载器中双亲委派模型的工作模式
总结摘要
JVM类加载器中双亲委派模型的工作模式
双亲委派模型是 JVM 类加载器的工作模式。要理解它,可以先想一个问题:当你写下 java.lang.String 时,怎么保证你加载的 String 是 JDK 的核心类,而不是你自己写的、可能包含恶意代码的 String?
双亲委派就是用来解决这个安全问题和避免重复加载的机制。
1. 核心概念:什么是双亲委派?
当一个类加载器收到类加载的请求时,它不会自己先去尝试加载这个类,而是把请求委派给父加载器去完成。每一个层级的加载器都是如此。只有当父加载器在自己的加载范围内找不到这个类时,子加载器才会自己尝试去加载。
这个过程可以总结为一句话:向上委托检查,向下自行加载。
2. 标准的类加载器层级
JVM 在运行时,有三层核心的类加载器:
启动类加载器(Bootstrap ClassLoader)
- 最顶层的加载器,由 C++ 实现(在 HotSpot 虚拟机中)。
- 负责加载
JAVA_HOME/jre/lib目录下的核心类库(如rt.jar中的java.lang.*)。 - 它是所有其他类加载器的“祖父”。
扩展类加载器(Extension ClassLoader)
- 负责加载
JAVA_HOME/jre/lib/ext目录下的类库。 - 它的父加载器是 Bootstrap ClassLoader。
- 负责加载
应用程序类加载器(Application ClassLoader)
- 我们最常接触的加载器。负责加载
Classpath(环境变量)下的类,也就是我们写的代码和引用的第三方 JAR 包。 - 它的父加载器是 Extension ClassLoader。
- 我们最常接触的加载器。负责加载
3. 工作流程演示
假设 JVM 需要加载 com.example.MyClass 和你代码中使用的 java.lang.String:
场景一:加载核心类 java.lang.String
- 开始请求:
Application ClassLoader收到加载java.lang.String的请求。 - 向上委托:它先不加载,而是问它的父加载器
Extension ClassLoader:“爸,你加载这个类吗?” - 继续向上:
Extension ClassLoader也不加载,继续问它的父加载器Bootstrap ClassLoader:“爷爷,你加载这个类吗?” - 顶层加载:
Bootstrap ClassLoader一看,java.lang.String正是它负责的(在rt.jar里),于是它加载了这个类。 - 返回结果:加载完成后,类信息沿着链条返回。
java.lang.String最终由最顶层的Bootstrap ClassLoader加载。
场景二:加载项目中的类 com.example.MyClass
- 开始请求:
Application ClassLoader收到加载com.example.MyClass的请求。 - 向上委托:
Application ClassLoader问父加载器Extension ClassLoader。 - 继续向上:
Extension ClassLoader问父加载器Bootstrap ClassLoader。 - 查找失败:
Bootstrap ClassLoader在rt.jar里找不到com.example.MyClass,返回“没找到”。 - 继续查找:
Extension ClassLoader在lib/ext目录下也找不到,也返回“没找到”。 - 自行加载:既然父加载器们都找不到,
Application ClassLoader就在项目的Classpath中找到了MyClass,并加载它。
4. 为什么这样设计?(好处)
沙箱安全(避免核心API被篡改)
- 假设你自己写了一个名为
java.lang.String的类,里面包含恶意代码。 - 根据双亲委派模型,加载
java.lang.String时,会一直向上委托到Bootstrap ClassLoader。 Bootstrap ClassLoader发现rt.jar里已经有标准的String了,就会加载标准的String,而不会去加载你写的那个。- 这就保证了 Java 核心类库的安全。
- 假设你自己写了一个名为
避免类的重复加载
- 当父加载器已经加载过某个类时,子加载器就不需要再加载一次。这保证了同一个类在 JVM 中只会有一份内存数据。
5. 一个容易混淆的点
- 这里的“双亲”并不是继承关系(extends),而是组合关系(Composition)。
- 类加载器中有一个属性叫
parent,用来指向它的父加载器。所以更准确的叫法应该是“父委托模型”。
6. 有没有破坏这个模型的情况?
有。常见的破坏场景包括:
- JDBC / JNDI 等 SPI(服务提供接口):
- 问题:这些是 Java 核心 API (
java.sql.DriverManager由Bootstrap ClassLoader加载),但实现类(如 MySQL 驱动)却在 Classpath 中(应由Application ClassLoader加载)。 - 解决:核心 API 无法加载具体实现。因此引入了 线程上下文类加载器(Thread Context ClassLoader),让核心 API 能“借用”应用程序类加载器去加载所需的实现类。这可以看作是一种对双亲委派模型的“逆向使用”。
- 问题:这些是 Java 核心 API (