JAVA--双亲委派机制
目录
什么是双亲委派机制
类加载器的层级结构
1. 启动类加载器(Bootstrap ClassLoader)
2. 扩展类加载器(Extension ClassLoader)
3. 应用程序类加载器(Application ClassLoader)
4. 自定义类加载器(Custom ClassLoader)
双亲委派机制的工作原理
为什么需要双亲委派机制
1. 避免类的重复加载
2. 保证Java核心API的安全性
3. 保证类的唯一性
双亲委派机制的源码分析
双亲委派机制的破坏
1. 自定义类加载器
2. 线程上下文类加载器
3. 热替换和热部署
实际应用场景
1. Web应用服务器
2. OSGi框架
3. 模块化系统
总结
什么是双亲委派机制
双亲委派机制(Parents Delegation Model)是JVM中类加载器的一种工作机制。当一个类加载器收到类加载请求时,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。只有当父类加载器无法完成加载请求时,子类加载器才会尝试自己去加载。
这种机制确保了Java核心API的类不会被随意替换,维护了Java运行环境的安全性和稳定性。
类加载器的层级结构
Java中的类加载器按照层级关系分为以下几种:
1. 启动类加载器(Bootstrap ClassLoader)
- 位置:JVM内部实现,由C++代码实现
- 作用:加载Java核心类库(如
java.lang.*
、java.util.*
等) - 路径:
$JAVA_HOME/lib
目录下的类库 - 特点:最顶层的类加载器,没有父类加载器
2. 扩展类加载器(Extension ClassLoader)
- 位置:
sun.misc.Launcher$ExtClassLoader
- 作用:加载扩展类库
- 路径:
$JAVA_HOME/lib/ext
目录下的类库 - 父类加载器:启动类加载器
3. 应用程序类加载器(Application ClassLoader)
- 位置:
sun.misc.Launcher$AppClassLoader
- 作用:加载应用程序类路径(ClassPath)上的类
- 路径:环境变量ClassPath指定的路径
- 父类加载器:扩展类加载器
- 特点:也称为系统类加载器
4. 自定义类加载器(Custom ClassLoader)
- 作用:用户根据需要自定义的类加载器
- 父类加载器:通常是应用程序类加载器
// 查看类加载器层级结构的示例代码
public class ClassLoaderHierarchy {public static void main(String[] args) {// 获取当前类的类加载器ClassLoader classLoader = ClassLoaderHierarchy.class.getClassLoader();System.out.println("当前类的类加载器:" + classLoader);// 获取父类加载器ClassLoader parentClassLoader = classLoader.getParent();System.out.println("父类加载器:" + parentClassLoader);// 获取祖父类加载器ClassLoader grandParentClassLoader = parentClassLoader.getParent();System.out.println("祖父类加载器:" + grandParentClassLoader);// 输出结果:// 当前类的类加载器:sun.misc.Launcher$AppClassLoader@2a139a55// 父类加载器:sun.misc.Launcher$ExtClassLoader@15db9742// 祖父类加载器:null (Bootstrap ClassLoader由C++实现,在Java中显示为null)}
}
双亲委派机制的工作原理
双亲委派机制的工作流程如下:
- 接收加载请求:类加载器接收到类加载请求
- 向上委派:不立即加载,而是委派给父类加载器
- 递归委派:父类加载器继续向上委派,直到启动类加载器
- 尝试加载:启动类加载器尝试加载类
- 向下返回:如果加载失败,返回给子类加载器尝试加载
- 最终加载:直到某个类加载器成功加载类或全部失败
graph TDA[自定义类加载器] --> B[应用程序类加载器]B --> C[扩展类加载器]C --> D[启动类加载器]D --> E{能否加载?}E -->|能| F[加载完成]E -->|不能| G[委派给子类加载器]G --> H{扩展类加载器能否加载?}H -->|能| I[加载完成]H -->|不能| J[委派给子类加载器]J --> K{应用程序类加载器能否加载?}K -->|能| L[加载完成]K -->|不能| M[委派给子类加载器]M --> N{自定义类加载器能否加载?}N -->|能| O[加载完成]N -->|不能| P[抛出ClassNotFoundException]
为什么需要双亲委派机制
1. 避免类的重复加载
如果没有双亲委派机制,每个类加载器都可能加载同一个类,导致内存中存在多个相同的类对象。
2. 保证Java核心API的安全性
防止核心API被恶意替换。例如,如果有人自定义了一个java.lang.String
类,通过双亲委派机制,最终会由启动类加载器加载JDK中的String
类,而不是用户自定义的类。
3. 保证类的唯一性
在JVM中,类的唯一性是由类加载器和类的全限定名共同决定的。双亲委派机制确保了同一个类只会被同一个类加载器加载一次。
双亲委派机制的源码分析
让我们来看看ClassLoader
类中loadClass
方法的实现:
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// 首先检查该类是否已经被加载Class<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {// 如果有父类加载器,委派给父类加载器加载if (parent != null) {c = parent.loadClass(name, false);} else {// 如果没有父类加载器,说明是启动类加载器c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// 如果父类加载器抛出ClassNotFoundException// 说明父类加载器无法完成加载请求}if (c == null) {// 如果父类加载器无法加载,则调用自己的findClass方法进行加载long t1 = System.nanoTime();c = findClass(name);// 记录加载时间统计sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}
双亲委派机制的破坏
虽然双亲委派机制很重要,但在某些场景下需要被破坏:
1. 自定义类加载器
通过重写loadClass
方法来改变类加载的行为:
public class CustomClassLoader extends ClassLoader {@Overrideprotected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {// 首先检查是否已经加载Class<?> c = findLoadedClass(name);if (c == null) {// 对于自定义的类,直接由当前类加载器加载if (name.startsWith("com.example.")) {c = findClass(name);} else {// 其他类仍然遵循双亲委派c = super.loadClass(name, resolve);}}if (resolve) {resolveClass(c);}return c;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {// 实现自定义的类加载逻辑byte[] classData = loadClassData(name);return defineClass(name, classData, 0, classData.length);}private byte[] loadClassData(String name) {// 从自定义位置加载类的字节码// 这里可以从网络、数据库等位置加载return null; // 简化示例}
}
2. 线程上下文类加载器
在某些情况下,父类加载器需要加载由子类加载器加载的类,这时可以使用线程上下文类加载器:
public class ContextClassLoaderExample {public static void main(String[] args) {// 获取当前线程的上下文类加载器ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();System.out.println("上下文类加载器:" + contextClassLoader);// 设置自定义的上下文类加载器Thread.currentThread().setContextClassLoader(new CustomClassLoader());// 在某些框架中,会使用上下文类加载器来加载类// 例如:JDBC驱动加载、Spring容器等}
}
3. 热替换和热部署
在开发环境中,为了实现热替换功能,需要破坏双亲委派机制:
public class HotSwapClassLoader extends ClassLoader {@Overrideprotected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {Class<?> c = findLoadedClass(name);if (c == null) {// 对于需要热替换的类,每次都重新加载if (isHotSwapClass(name)) {c = findClass(name);} else {c = super.loadClass(name, resolve);}}if (resolve) {resolveClass(c);}return c;}private boolean isHotSwapClass(String name) {// 判断是否是需要热替换的类return name.startsWith("com.example.hotswap");}
}
实际应用场景
1. Web应用服务器
Tomcat等Web服务器为了实现应用隔离,每个Web应用都有自己的类加载器:
// Tomcat的类加载器层级结构
// Bootstrap ClassLoader
// |
// System ClassLoader
// |
// Common ClassLoader
// |
// Catalina ClassLoader Shared ClassLoader
// |
// WebApp ClassLoader
2. OSGi框架
OSGi框架完全破坏了双亲委派机制,实现了网状的类加载器结构。
3. 模块化系统
Java 9的模块系统也对双亲委派机制进行了一定的改进。
总结
双亲委派机制是Java类加载器的核心机制,它具有以下特点:
优点:
- 避免类的重复加载
- 保证Java核心API的安全性
- 维护类的唯一性
缺点:
- 在某些场景下过于严格,需要被破坏
- 可能导致类加载的性能问题
适用场景:
- 大部分标准Java应用
- 需要保证类加载安全性的场景
破坏场景:
- 自定义类加载器
- 热替换和热部署
- 模块化系统
- Web应用服务器