Java 类加载机制:双亲委派模型与打破双亲委派
一、引言
Java 类加载机制是 Java 运行时环境的重要组成部分,它负责将类的字节码文件加载到 Java 虚拟机(JVM)中。类加载机制不仅保证了 Java 程序的安全性和稳定性,还为 Java 的动态扩展性提供了支持。本文将深入探讨 Java 类加载机制中的双亲委派模型以及打破双亲委派的相关知识。
二、类加载器概述
在 Java 中,类加载器(ClassLoader)是实现类加载的核心组件。Java 提供了几种不同类型的类加载器,它们各自负责加载不同范围的类:
- 启动类加载器(Bootstrap ClassLoader):它是最顶层的类加载器,由 C++ 实现,负责加载 Java 的核心类库,如
java.lang、java.util等。启动类加载器无法被 Java 代码直接引用。 - 扩展类加载器(Extension ClassLoader):它是由 Java 代码实现的,继承自
URLClassLoader,负责加载 Java 的扩展类库,通常位于jre/lib/ext目录下。 - 应用程序类加载器(Application ClassLoader):也称为系统类加载器,同样由 Java 代码实现,继承自
URLClassLoader,负责加载用户类路径(classpath)上的类。一般情况下,我们自己编写的 Java 类都是由应用程序类加载器加载的。 - 自定义类加载器(Custom ClassLoader):用户可以通过继承
ClassLoader类来实现自己的类加载器,用于加载特定来源的类,如从网络、数据库等加载类。
三、双亲委派模型
3.1 模型原理
双亲委派模型是 Java 类加载机制的核心原则。当一个类加载器收到类加载请求时,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。每一层的类加载器都是如此,因此所有的类加载请求最终都会传送到最顶层的启动类加载器。只有当父类加载器无法完成加载任务时,子加载器才会尝试自己去加载该类。
3.2 工作流程
以下是双亲委派模型的详细工作流程:
- 当一个类加载器(如应用程序类加载器)收到类加载请求时,它会先检查该类是否已经被加载过。如果已经加载过,则直接返回该类的
Class对象;否则,进入下一步。 - 将类加载请求委派给其父类加载器(如扩展类加载器)。
- 父类加载器重复步骤 1 和 2,继续将请求向上委派,直到到达启动类加载器。
- 启动类加载器尝试加载该类。如果能够加载,则返回该类的
Class对象;如果无法加载,则将请求返回给子类加载器。 - 子类加载器(如扩展类加载器)收到返回的请求后,尝试自己加载该类。如果能够加载,则返回该类的
Class对象;如果无法加载,则继续将请求返回给下一级子类加载器。 - 重复步骤 5,直到最开始发起请求的类加载器(如应用程序类加载器)尝试加载该类。如果仍然无法加载,则抛出
ClassNotFoundException异常。
3.3 示例代码
import java.net.URL;
import java.net.URLClassLoader;
public class ClassLoaderExample {
public static void main(String[] args) {
// 获取应用程序类加载器
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
System.out.println("应用程序类加载器: " + appClassLoader);
// 获取扩展类加载器
ClassLoader extClassLoader = appClassLoader.getParent();
System.out.println("扩展类加载器: " + extClassLoader);
// 获取启动类加载器
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println("启动类加载器: " + bootstrapClassLoader);
try {
// 尝试加载一个类
Class<?> clazz = appClassLoader.loadClass("java.lang.String");
System.out.println("类 " + clazz.getName() + " 由 " + clazz.getClassLoader() + " 加载");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
在上述代码中,我们尝试加载 java.lang.String 类,根据双亲委派模型,该类最终会由启动类加载器加载。
3.4 优点
- 安全性:双亲委派模型保证了 Java 核心类库的安全性。例如,用户无法自定义一个与
java.lang.String同名的类来替换 Java 核心类库中的String类,因为启动类加载器会优先加载核心类库中的类。 - 避免重复加载:通过委派机制,避免了类的重复加载,提高了类加载的效率。
四、打破双亲委派
4.1 打破原因
在某些特殊场景下,双亲委派模型可能无法满足需求,需要打破双亲委派机制。例如:
- 热部署:在开发过程中,需要在不重启应用的情况下更新类的代码。
- 类隔离:在一些框架中,需要实现不同模块之间的类隔离,每个模块使用独立的类加载器加载类。
4.2 打破方式
要打破双亲委派机制,需要自定义类加载器,并重写 loadClass 方法。在重写的 loadClass 方法中,不遵循双亲委派的规则,直接加载所需的类。
4.3 示例代码
import java.io.*;
public class CustomClassLoader extends ClassLoader {
private String classPath;
public CustomClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 检查类是否已经被加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// 自定义加载逻辑,不委派给父类加载器
byte[] classData = loadClassData(name);
if (classData != null) {
c = defineClass(name, classData, 0, classData.length);
}
} catch (IOException e) {
e.printStackTrace();
}
}
if (c == null) {
// 如果自定义加载失败,再调用父类的加载方法
c = super.loadClass(name, resolve);
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
private byte[] loadClassData(String className) throws IOException {
String path = classPath + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
try (InputStream is = new FileInputStream(path);
ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) != -1) {
bos.write(buffer, 0, length);
}
return bos.toByteArray();
}
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
CustomClassLoader customClassLoader = new CustomClassLoader("path/to/classes");
Class<?> clazz = customClassLoader.loadClass("com.example.MyClass");
Object obj = clazz.newInstance();
System.out.println("对象 " + obj + " 由 " + clazz.getClassLoader() + " 加载");
}
}
在上述代码中,我们自定义了一个类加载器 CustomClassLoader,并重写了 loadClass 方法。在 loadClass 方法中,我们首先尝试自己加载类,而不是将请求委派给父类加载器,从而打破了双亲委派机制。
五、总结
双亲委派模型是 Java 类加载机制的核心原则,它保证了 Java 程序的安全性和稳定性。但在某些特殊场景下,需要打破双亲委派机制,通过自定义类加载器并重写 loadClass 方法来实现。理解 Java 类加载机制和双亲委派模型,有助于更好地开发和调试 Java 程序,同时也为实现一些高级功能(如热部署、类隔离)提供了基础。
333

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



