
JAVA双亲委派机制详解
什么是双亲委派机制
双亲委派机制(Delegation Model)是Java类加载器(ClassLoader)的一种工作模式。该机制要求类加载器在加载类时,首先将加载请求委托给父类加载器完成,只有当父类加载器无法完成加载时,子类加载器才会尝试自己加载。
双亲委派机制的工作流程详解
1. 检查类是否已加载
当类加载器收到加载类的请求时,首先会检查自己的缓存中是否已经加载过该类。这个检查通常通过查询类加载器的已加载类列表(如Java中的findLoadedClass方法)来完成。如果发现该类已经被加载,则直接返回已存在的Class对象,避免重复加载。
示例场景:假设应用程序需要加载java.lang.String类,Bootstrap类加载器会先在已加载类列表中查找。
2. 委托给父类加载器
如果该类未被加载过,当前类加载器不会立即尝试加载,而是将加载请求委托给它的父类加载器(parent classloader)。在Java中,这种委托关系通常是层次化的:
- 应用程序类加载器(AppClassLoader)的父加载器是扩展类加载器(ExtClassLoader)
- 扩展类加载器的父加载器是启动类加载器(Bootstrap ClassLoader)
关键点:这种委托机制确保了核心Java类库由最高层的类加载器加载,维护了Java的安全模型。
3. 父类加载器递归处理
父类加载器在收到请求后,同样会遵循双亲委派机制:
- 先检查自己是否已加载过该类
- 如果未加载,继续向其父类加载器委托(如果有)
- 这个过程一直递归到最顶层的启动类加载器
典型流程:
- 应用程序需要加载类X
- AppClassLoader委托给ExtClassLoader
- ExtClassLoader委托给Bootstrap ClassLoader
- Bootstrap ClassLoader尝试加载(通常从jre/lib目录)
4. 尝试自己加载
如果所有父类加载器都无法加载该类(通常因为它们在其搜索路径中找不到该类),则加载请求会逐级返回至最初发起请求的类加载器,由该加载器尝试加载。
加载方式差异:
- Bootstrap ClassLoader:加载JRE核心类库(如rt.jar)
- ExtClassLoader:加载JRE扩展目录(如jre/lib/ext)中的类
- AppClassLoader:加载应用程序类路径(classpath)中的类
5. 抛出ClassNotFoundException
如果当前类加载器在其搜索路径中也无法找到并加载该类,则会抛出ClassNotFoundException。这通常意味着:
- 类名拼写错误
- 类文件不在类路径中
- 类文件已损坏
- 类加载器的搜索范围受限
异常处理建议:在实际开发中,应该检查类路径配置、依赖版本和文件完整性,确保所需类文件可用。
Java类加载器层次结构
Java中的类加载器分为以下几种:
-
Bootstrap ClassLoader(启动类加载器):
- 最顶层的类加载器,由C++实现
- 负责加载JAVA_HOME/lib目录下的核心类库,如rt.jar
- 是Extension ClassLoader的父类加载器
-
Extension ClassLoader(扩展类加载器):
- 负责加载JAVA_HOME/lib/ext目录下的扩展类库
- 是System ClassLoader的父类加载器
-
System ClassLoader/Application ClassLoader(系统/应用类加载器):
- 负责加载用户类路径(classpath)上的类库
- 是自定义类加载器的父类加载器
-
Custom ClassLoader(自定义类加载器):
- 用户自己定义的类加载器
- 继承自java.lang.ClassLoader类
双亲委派机制的代码实现
双亲委派机制的核心代码体现在ClassLoader类的loadClass方法中:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 首先检查类是否已加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 委托给父类加载器
c = parent.loadClass(name, false);
} else {
// 没有父类加载器,使用Bootstrap ClassLoader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父类加载器无法完成加载
}
if (c == null) {
// 父类加载器无法加载,尝试自己加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
双亲委派机制的优势
- 避免重复加载:确保类只被加载一次,防止内存中出现多份相同的字节码
- 安全性:防止核心API被篡改,例如用户自定义一个java.lang.String类不会被加载
- 稳定性:保证Java程序的稳定运行,避免因为类版本冲突导致的问题
打破双亲委派机制的场景
-
SPI(Service Provider Interface)机制:如JDBC驱动加载
- 核心类(如DriverManager)需要加载实现类(如MySQL驱动)
- 使用Thread Context ClassLoader(线程上下文类加载器)实现
-
OSGi框架:实现模块化热部署
- 每个模块(Bundle)有自己的类加载器
- 当需要加载类时,先在本模块内查找,再委托给父类加载器
-
Tomcat等Web容器:
- 每个Web应用有自己的类加载器
- 优先加载Web应用自己的类,再委托给父类加载器
实际应用示例
以JDBC驱动加载为例说明打破双亲委派机制:
// 传统JDBC加载方式
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, user, password);
// 实际上DriverManager会通过ServiceLoader加载所有驱动
// 使用线程上下文类加载器打破双亲委派
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
总结
双亲委派机制是Java类加载的基础模型,它保证了Java程序的稳定性和安全性。但在一些特殊场景下,如SPI、OSGi等,会打破这种机制来实现特定的功能需求。理解双亲委派机制对于深入掌握Java类加载原理和解决类加载相关的问题非常重要。
Java双亲委派机制详解
1004

被折叠的 条评论
为什么被折叠?



