当前位置: 首页 > news >正文

Java 的双亲委派模型(Parent Delegation Model)

文章目录

    • Java 的双亲委派模型(Parent Delegation Model)
      • 一、什么是双亲委派机制?
      • 二、为什么要用这种机制?(优点)
      • 三、类加载器的层次结构(谁是谁的“双亲”?)
      • 四、工作流程(它是如何工作的?)
      • 五、如何打破双亲委派?
      • 总结

Java 的双亲委派模型(Parent Delegation Model)

一、什么是双亲委派机制?

双亲委派模型是 Java 类加载器(ClassLoader)在加载一个类时所采用的一种工作模式。它的核心思想可以概括为:

当一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。每一层的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器(Bootstrap ClassLoader)中。只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

简单来说,就是 “先让爸爸来,爸爸不行我再上”


二、为什么要用这种机制?(优点)

双亲委派机制绝不是随意设计的,它解决了几个关键问题:

  1. 避免类的重复加载(确保唯一性)
    通过委派链,一个类会被尽可能高地由某个父加载器加载。这确保了在整个 JVM 中,同一个类(由类加载器实例 + 类的全限定名共同标识)只会被加载一次,从而避免了多份相同的字节码在内存中同时存在。

  2. 保护程序安全,防止核心API被篡改(沙箱安全机制)
    这是最重要的原因。比如你自己写了一个java.lang.Object类,如果没有双亲委派,这个类可能会被应用程序类加载器加载,从而覆盖掉 Java 核心库中的Object类,这将极其危险。
    有了双亲委派,你的java.lang.Object加载请求会一路向上委派给启动类加载器。启动类加载器在它的路径(rt.jar等)下找到了真正的Object类并加载它,于是你的那个“恶意”的Object类就根本没有机会被加载。这保证了 Java 核心库的类型安全。

  3. 保证程序的稳定和有序
    它确立了一套带有优先级的层次关系,使得类的加载变得非常有序。


三、类加载器的层次结构(谁是谁的“双亲”?)

Java 中的类加载器主要分为以下三层(以 Java 8 为例),它们之间不是继承关系,而是组合关系(子加载器中有个parent字段指向父加载器):

  1. Bootstrap ClassLoader(启动类加载器)

    • 最高层级,由 C++ 实现,是 JVM 的一部分。
    • 负责加载 Java 的核心库(JAVA_HOME/jre/lib/rt.jar, resources.jar等),如java.lang.*, java.util.*等。
    • 它是所有其他类加载器的“祖先”(它是ExtClassLoader的父加载器)。
  2. Extension ClassLoader(扩展类加载器)

    • 由 Java 实现,是sun.misc.Launcher$ExtClassLoader类。
    • 负责加载 Java 的扩展库(JAVA_HOME/jre/lib/ext目录,或者由java.ext.dirs系统变量指定的路径中的所有类库)。
  3. Application ClassLoader(应用程序类加载器/系统类加载器)

    • 由 Java 实现,是sun.misc.Launcher$AppClassLoader类。
    • 负责加载用户类路径(ClassPath)上所指定的类库。
    • 一般情况下,我们程序中默认的类加载器就是它。可以通过ClassLoader.getSystemClassLoader()获取。
  4. 自定义类加载器(Custom ClassLoader)

    • 用户可以自己定义类加载器,继承自java.lang.ClassLoader,来实现特殊的加载需求(如从网络、加密文件中加载)。

关系图:

                Bootstrap ClassLoader (C++实现,无Java对象)↑Extension ClassLoader (sun.misc.Launcher$ExtClassLoader)↑Application ClassLoader (sun.misc.Launcher$AppClassLoader)↑自定义类加载器 (MyClassLoader) -> 自定义类加载器2...

注意:这里的“父”子关系是组合关系,不是继承关系。


四、工作流程(它是如何工作的?)

双亲委派的工作流程体现在ClassLoader.loadClass(String name, boolean resolve)方法的默认实现中:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// 1. 首先,检查请求的类是否已经被这个类加载器加载过了Class<?> c = findLoadedClass(name);if (c == null) {try {// 2. 如果父加载器不为空,则委派给父加载器去加载if (parent != null) {c = parent.loadClass(name, false); // 关键:递归调用!} else {// 3. 如果父加载器为空(说明父加载器是Bootstrap),则委派给Bootstrap加载c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// 父加载器抛出异常,表示无法完成加载}// 4. 如果父加载器(及以上的所有父加载器)都无法完成加载if (c == null) {// 5. 那么才调用自己的findClass方法去尝试加载c = findClass(name);}}if (resolve) {resolveClass(c);}return c;}
}

流程步骤拆解:
假设我们要用AppClassLoader加载一个用户自定义的com.example.MyClass

  1. AppClassLoader首先检查自己是否已经加载过com.example.MyClass,如果没有,则调用parent.loadClass(),即委派给ExtClassLoader
  2. ExtClassLoader同样,先检查自己是否加载过,如果没有,则调用它的parent.loadClass()。但ExtClassLoader的父加载器是Bootstrap ClassLoader(在代码中体现为parentnull)。
  3. Bootstrap ClassLoader(在代码中是findBootstrapClassOrNull逻辑)在它的核心库路径下查找com.example.MyClass,显然找不到,返回null
  4. ExtClassLoader发现父加载器Bootstrap没找到,于是开始用自己的findClass()方法在扩展库路径(ext目录)下查找,也找不到。
  5. AppClassLoader收到ExtClassLoader也加载失败的通知(返回null),于是开始用自己的findClass()方法在用户类路径(ClassPath)下查找。
  6. 最终,AppClassLoaderclasspath下找到了com.example.MyClass.class文件,将其加载到内存并生成Class对象。

五、如何打破双亲委派?

双亲委派模型不是一个强制性的约束,而是 Java 设计者推荐给开发者的一种类加载器实现方式。在复杂的环境中,有时需要打破它。

打破的方式就是重写loadClass()方法。因为双亲委派的逻辑就封装在这个方法里。如果你重写了它,并且不调用parent.loadClass(),那就意味着打破了委派链。

历史上打破双亲委派的例子:

  1. JDBC SPI (Service Provider Interface)

    • java.sql.Driver接口在rt.jar中,由Bootstrap ClassLoader加载。
    • 各个数据库厂商的实现(如mysql-connector-java.jar中的驱动)在ClassPath下,理应由AppClassLoader加载。
    • 但根据双亲委派,Bootstrap ClassLoader无法“看到”AppClassLoader加载的类,这就产生了问题。
    • 解决方案是引入了线程上下文类加载器(Thread Context ClassLoader),它可以将加载SPI实现的类加载器(通常是AppClassLoader)“设置”到线程中,这样核心库的代码(由Bootstrap加载)就可以通过这个上下文类加载器去加载第三方实现。这是一种“父级委托子级”的反常操作,打破了双亲委派。
  2. OSGi、Tomcat 等框架

    • 这些容器/module系统有更复杂的类隔离和热部署需求,它们都重写了loadClass()逻辑,实现了自己独特的加载规则(如优先加载自己模块下的类,找不到再委派),完全打破了双亲委派。

总结

特性说明
核心思想自底向上委派,自顶向下尝试加载。
目的保证类的唯一性、安全性和稳定性。防止核心API被篡改。
关键方法ClassLoader.loadClass()(委派逻辑在此),findClass()(自定义加载逻辑应重写此方法)。
如何打破重写loadClass()方法,不调用父类的实现。
常见场景JDBC SPI使用线程上下文类加载器打破;OSGi、Tomcat为实现模块化/热部署而打破。
http://www.dtcms.com/a/390345.html

相关文章:

  • ​​[硬件电路-249]:LDO(低压差线性稳压器)专用于线性电源,其核心设计逻辑与线性电源高度契合,而与开关电源的工作原理存在本质冲突。
  • conda命令行指令大全
  • TCP三次握手与四次挥手
  • Python读取Excel中指定列的所有单元格内容
  • 【DMA】DMA入门:理解DMA与CPU的并行
  • Redis数据库(一)—— 初步理解Redis:从基础配置到持久化机制
  • Salesforce中的事件驱动架构:构建灵活可扩展的企业应用
  • OpenCV实现消除功能
  • Qt QValueAxis详解
  • deepseek大模型部署
  • 消息队列与定时器:如何优雅地处理耗时任务?
  • Maya绑定基础知识总结合集:父子关系和父子约束对比、目标约束示例
  • STM32开发(中断模式:外部中断)
  • (圆方树)洛谷 P4630 APIO2018 铁人两项 题解
  • windows10 使用moon-pilot并配置模型
  • Linux笔记---epoll用法及原理:从内核探究文件等待队列的本质-回调机制
  • Python快速入门专业版(三十三):函数参数陷阱:默认参数的“可变对象”问题(避坑指南)
  • Spring Security 框架 实践小项目(实现不同用户登录显示不同菜单以及每个菜单不同权限)
  • 开发避坑指南(49):Java Stream 对List中的字符串字段求和
  • 网络编程day02-组播,广播
  • 前端左侧菜单列表怎么写
  • LLM大模型和文心一言、豆包、deepseek对比
  • stm32h743iit6 配置 FMC 的时钟源
  • 中小企业数字化转型:从工具升级到思维转变
  • 数据传输中的三大难题,ETL 平台是如何解决的?
  • DAY16 字节流、字符流、IO资源的处理、Properties、ResourceBundle
  • 电气工程师面试题及答案
  • Halcon一维码与二维码识别技术解析
  • 【数据库系统Trip 第1站】总概
  • 关于 Python 编程语言常见问题及技术要点的说明