6. 什么是双亲委派模型?它如何确保类的安全加载?
双亲委派模型(Parent Delegation Model) 是 Java 中类加载机制的重要设计模式。它通过分层次的类加载器结构和委派机制,确保了 Java 类的安全加载,防止核心类库被篡改或替换。
1. Java 类加载器
在 Java 中,类加载器负责将 .class
文件加载到 JVM 中。Java 中有几种主要的类加载器:
- Bootstrap ClassLoader(启动类加载器):
- 是 JVM 自带的最顶层的类加载器,用于加载核心类库(如
rt.jar
),如java.lang.*
、java.util.*
等核心类。 - 它是用原生代码实现的,负责加载 JDK 中的核心类。
- 是 JVM 自带的最顶层的类加载器,用于加载核心类库(如
- Extension ClassLoader(扩展类加载器):
- 负责加载扩展类库,通常位于
JAVA_HOME/lib/ext
目录下的类库。 - 它由 Java 实现,通常是
sun.misc.Launcher$ExtClassLoader
的实例。
- 负责加载扩展类库,通常位于
- Application ClassLoader(应用程序类加载器):
- 也称为系统类加载器,负责加载应用程序类路径(
CLASSPATH
)下的类和库。 - 它也是由 Java 实现的,通常是
sun.misc.Launcher$AppClassLoader
的实例。
- 也称为系统类加载器,负责加载应用程序类路径(
2. 双亲委派模型的工作原理
双亲委派模型的核心思想是:一个类加载器在加载类时,首先将类加载请求委派给父类加载器进行加载。只有当父类加载器无法完成加载时,才由子加载器自己加载。
具体步骤如下:
- 类加载请求:当某个类加载器(例如
Application ClassLoader
)接收到加载类的请求时,首先不会自己尝试加载,而是将请求委派给它的父类加载器(例如Extension ClassLoader
)。 - 逐级委派:
Extension ClassLoader
同样会将请求委派给它的父类加载器,即Bootstrap ClassLoader
。 - 尝试加载:
Bootstrap ClassLoader
会尝试加载这个类。如果加载成功,则加载过程结束,类加载器链中的所有加载器都会共享这个类。 - 无法加载:如果
Bootstrap ClassLoader
无法加载该类,加载请求会返回给Extension ClassLoader
,然后Extension ClassLoader
会尝试加载。 - 逐级返回:如果
Extension ClassLoader
也无法加载,加载请求最终会返回给最初的Application ClassLoader
,由它尝试加载。 - 加载失败:如果所有类加载器都无法加载该类,则抛出
ClassNotFoundException
。
3. 双亲委派模型的优势
1. 确保核心类库安全
双亲委派模型能够有效防止核心类库被篡改或替换。例如,java.lang.String
是由 Bootstrap ClassLoader
加载的,如果应用程序中的类加载器想要加载自己定义的 java.lang.String
类,它的加载请求会首先被委派给 Bootstrap ClassLoader
。由于 Bootstrap ClassLoader
已经加载了真正的 String
类,因此应用程序无法加载一个伪造的 String
类。这种机制保证了 Java 核心类库的安全性和一致性。
2. 避免类的重复加载
双亲委派模型还能够避免类的重复加载。通过委派机制,JVM 能够确保同一个类在同一个类加载器层次中只被加载一次,从而节省内存,并且防止类的重复定义导致的冲突。
4. 双亲委派模型的实现与应用
在 Java 中,自定义类加载器时,通常通过继承 ClassLoader
类并重写 findClass()
方法来实现。ClassLoader
类本身已经实现了双亲委派机制。
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 先委派给父类加载器进行加载
return super.findClass(name);
}
}
自定义类加载器在调用 findClass()
方法之前,会首先调用 loadClass()
方法,而 loadClass()
方法已经内置了双亲委派逻辑。一般情况下,loadClass()
方法不需要被重写。
5. 双亲委派模型的局限性与灵活性
尽管双亲委派模型能够有效保护核心类库,防止类的重复加载,但在某些情况下,需要打破这个模型,例如:
- 类热替换:一些框架(如 OSGi、JSP、Tomcat 的 Web 应用热部署)可能需要支持类的热替换或多版本共存,这些场景下可能需要自定义类加载器,不使用双亲委派模型。
- 插件机制:某些插件框架可能需要加载与主程序版本不同的库,这时需要灵活调整类加载器策略。
为了实现这些需求,自定义类加载器可以选择不完全遵循双亲委派模型,直接在子类加载器中实现类的加载逻辑。
总结
双亲委派模型 是 Java 类加载机制的重要组成部分,通过委派机制保证了核心类库的安全加载,避免了类的重复加载问题。它是 Java 运行时环境的安全性和稳定性的关键保证。在日常 Java 开发中,大多数情况下都无需修改双亲委派模型,除非在一些特定场景下,如插件机制或类热替换等,需要自定义类加载器的行为。