【JVM】已验鼎真,鉴定为:妈妈加载的(双亲委派模型)
在Java的世界中,类加载器(ClassLoader)是Java虚拟机(JVM)用来动态加载类的基础组件。双亲委派模型(Parent Delegation Model)是Java类加载机制中一个至关重要的设计,它确保Java类加载过程的安全性和稳定性。本文将简单介绍JVM中的类加载器机制及其双亲委派模型。
首先我们来看看类加载器。
一、类加载器概述
Java中的类加载器主要有以下几种:
- Bootstrap ClassLoader(引导类加载器):
- 由C++实现,是JVM自身的一部分。
- 负责加载Java的核心类库,例如
rt.jar
中的类。
- Extension ClassLoader(扩展类加载器):
- 由
sun.misc.Launcher$ExtClassLoader
实现。 - 负责加载Java扩展库(JDK扩展目录
lib/ext
中的JAR包)。
- 由
- 应用程序类加载器(Application Class Loader):
- 由
sun.misc.Launcher$AppClassLoader
实现。 - 负责加载应用程序的类路径(classpath)上的类。
- 由
- User-defined ClassLoader(用户自定义类加载器):
- 通过继承
java.lang.ClassLoader
可以实现自定义类加载器。 - 用于加载特定需求的类,例如网络类加载器、数据库类加载器等。
- 通过继承
二、双亲委派模型
双亲委派模型,准确来说,即当一个类加载器收到一个类的加载请求时,首先不会自己尝试去加载它,而是把这一请求委派给父类加载器去完成,这样层层委派,因此所有的加载请求都最终会被传送到顶层的启动类加载器中。
只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。
- 检查缓存:类加载器首先检查自己是否已经加载过该类,如果加载过则直接返回。
- 委派父类加载器:如果自己没有加载过,则将加载请求委派给父类加载器。
- 递归委派:父类加载器同样遵循上述步骤,递归检查其父类加载器,直到顶层的Bootstrap ClassLoader(启动类加载器)。
- 加载类:如果顶层的类加载器无法加载该类,则依次往下返回,由各级子类加载器尝试加载该类。
- 缓存加载结果:成功加载类后,将该类缓存以便下次直接使用。
双亲委派模型的优点
- 安全性:通过双亲委派机制,可以防止核心类库被篡改。例如,Java核心类库中的
java.lang.String
类只有由Bootstrap ClassLoader加载,防止应用程序自定义一个java.lang.String
类来替换。 - 避免重复加载:确保同一个类只被加载一次。即使在不同的类加载器中,只要遵循双亲委派机制,类加载器会避免重复加载同一个类。
- 模块化:使得类加载器可以在一个隔离的环境中加载类,适应不同的需求,例如应用服务器中不同应用的隔离运行。
双亲委派模型的缺点
- 灵活性不足:在某些情况下,双亲委派模型的严格父子关系限制了类加载器的灵活性。例如,开发人员可能希望在应用程序中加载一个与系统类库版本不同的库。
- 调试困难:由于类加载过程涉及多个类加载器的协同工作,调试类加载问题时可能比较复杂,尤其是在遇到类加载冲突或类版本不一致的情况。
三、如何打破双亲委派模型
正常加载类的顺序,是用户自定义类加载器 -> 应用程序类加载器 -> 扩展类加载器 -> 引导类加载器,如果不遵循这个顺序,就是在打破双亲委派机制。
而双亲委派过程都是在loadClass方法中实现的,如果想要破坏这种机制,那么就自定义一个类加载器,重写其中的loadClass方法,使其不进行双亲委派即可。
例如:
public class CustomClassLoader extends ClassLoader {
public CustomClassLoader(ClassLoader parent) {
super(parent);
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 不委派给父类加载器,直接尝试加载目标类
try {
// 尝试从当前类加载器的资源路径加载类
byte[] classData = loadClassData(name);
if (classData != null) {
return defineClass(name, classData, 0, classData.length);
}
} catch (Exception e) {
// 忽略加载异常
}
// 如果当前类加载器无法加载,委派给父类加载器
return super.loadClass(name);
}
private byte[] loadClassData(String name) {
// 从文件或其他资源中读取类字节码的逻辑
// ...
return null; // 实际实现时应返回有效的字节码数据
}
}
四、双亲委派模型的示例
以Tomcat为例,Apache Tomcat是一个广泛使用的Java Web服务器,一个web容器可能需要部署多个应用程序,不同的应用程序可能会依赖同一个第三方库的不同版本,但是不同版本的库中某一个类的全路径名可能是一样的。
它提供了一种机制来打破双亲委派模型,以支持不同Web应用之间的类隔离。Tomcat中,每个Web应用都有自己的类加载器,并且Tomcat的类加载机制允许在特定情况下打破双亲委派模型。
Tomcat的类加载器架构如下:
- Bootstrap ClassLoader:加载Java核心类库。
- System ClassLoader:加载Tomcat自身的类库。
- Common ClassLoader:加载Tomcat的共享类库。
- Webapp ClassLoader:每个Web应用有独立的类加载器,用于加载该应用的类和库。
如果采用默认的双亲委派类加载机制,无法加载多个相同的类。
所以,Tomcat破坏双亲委派原则,提供隔离的机制,为每个web容器单独提供一个WebAppClassLoader加载器,每个应用都有自己的类加载器WebAppClassLoader,该加载器重写了loadClass方法,会优先加载当前应用下的类,加载不到时再交给WebAppClassLoader的父加载器SharedClassLoader去加载。
打破双亲委派模型是一种特殊的需求,通常用于解决特定的类加载冲突或版本兼容问题。在Tomcat等应用服务器中,通过自定义类加载器和配置,可以实现对类加载过程的精细控制。理解和灵活应用类加载机制,可以帮助开发人员更好地管理和优化Java应用程序的运行环境。
结语
双亲委派机制属于类加载机制的后续,本来应该很早就发出来力,因为内容不多,但是因为笔者很懒所以拖到现在啦~属于是终于填坑了属于是。
参考文献
深入浅出Java类加载机制(双亲委派模型)与自定义类加载器实践_java类的装载机制-CSDN博客
java—双亲委派模型_java中双亲委派模型-CSDN博客