文章内容引用自 咕泡科技
咕泡出品,必属精品

首先我们要知道双亲委派机制是为了解决什么问题?

双亲委派机制的目的:
为了安全,保证核心类库的安全性,防止被篡改,以及保证类的唯一

怎么保证类的唯一呢?
默认的情况下,一个限定名的类只会被一个类加载器加载解析并使用,这样在程序中,它就是唯一的,不会产生歧义。

有关类加载器,可以参考我的这篇博客:

双亲委派

  所谓的双亲委派,就是先让父亲加载器试图加载该Class,只有在父亲加载器无法加载该类时才尝试从自己的类路径中加载该类。
  通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父亲加载器,依次递归,如果父亲加载器可以完成类加载任务,就成功返回;
  只有父亲加载器无法完成此加载任务时,才自己去加载。

我们通过这张图来理解一下:

在被动的情况下,当一个类加载器收到加载请求,他不会首先自己去加载,而是传递给自己的父亲加载器。

这样所有的类都会首先传递到最上层的Bootstrap ClassLoader,只有父亲加载器无法完成加载,那么此时儿子加载器才会自己去尝试加载。

什么叫无法父亲加载器加载呢?
就是根据类的限定名,类加载器没有在自己负责的加载路径中找到该类。

 这里注意,我没有用父类加载器、子类加载器这样的语句,而是使用了**父亲加载器**,因为上图中这些箭头并**不表示继承关系**,而是一种**逻辑关系**,实际上是通过组合的方式来实现的,这也是很多博客上没有写清楚,容易误导人的一点。

通过源码来看一下双亲委派具体是怎么实现的

代码很简单,来看java.lang.Class Loader中的load class方法

首先呢检查该类是否已经被加载了,如果没有,则开启加载流程,如果有,那么就直接读取缓存。

缓存机制
缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。
这就是为很么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。

parent变量代表了当前class loader 的父亲加载器,这里就体现了不是通过继承,而是通过组合的方式实现类加载器之间的父子关系

如果parent等于null,那这里要说一下,当parent等于null 代表parent 为bootstrap classloader 。我们开头也讲过,bootstrap classloader是由jvm内部实现的,没有办法被程序引用,所以这里约定为null。当parent为null,就调用findBootstrapClassOrNull这个方法,让BootstrapClassLoader尝试进行加载,如果parent不为null 就让parent根据类的全限定名尝试加载,并返回该类。如果返回的class为null 说明parent加载不了,使用当前的loader的findclass方法尝试加载这个全限定名的class,需要类加载器自己去实现。

比如Extension ClassLoader 和 Application ClassLoader都使用以下这段逻辑来实现findClass

这里呢可以看到,通过类的全限定名定位到class文件,然后通过ucp这个对象去查找,找到文件的资源后,再调用 defineClass去进行类加载的后续流程。defineClass方法是ClassLoader中一个被final修饰的方法,意味着获取到class文件的二进制流后,最终会由java.lang.classloader来进行操作,因为它是被final修饰的,意味着是不能够被外部重写的。这个符合了我们最开始所说的类的加载过程中除了读取二进制流憎操作外,剩余的逻辑都是由jvm内部实现的,双亲委派机制就是这么简单。

打破双亲委派机制

在大部分情况下,双亲委派机制是能够生效并且是能按预期执行的。

1第一次被破坏

在一些情况下,双亲委派机制是可以主动破坏的,细心的同学可以发现,我自己通过匿名内部类直接重写了java.lang.classloader的load class方法,而我们的双亲委派机制是存在于这个方法内的。那么我们这次重写就是对原有的双亲委派机制的逻辑破坏,所以也出现同一个限定名出现了两个不同classloader进行load的情况。

本图取自咕泡学院,如有侵权,联系速删

除非是有特殊的业务场景,一般来说不要主动去破坏双亲委派模型

那有的人可能会有疑问啦,既然jvm推荐并希望开发者遵循双亲尾派模型,那么为什么不把load class方法像defineClass设定成final来修饰?

那这样的话我这边就没有办法重写,也就代表着上层开发者需要尽可能的遵循双亲委派的逻辑了。
这是JVM无法解决的问题

java.lang.ClassLoader 的load class方法呢在java很早的版本就有了,而双亲委派模型是在jdk1.2引入的特性。

java是向下兼容的,也就是说引入双擎委派机制时呢,世界上已经存在了很多像我上面一样的代码,那么jvm只能向下兼容的,只能提出折中的解决方式。

这个解决的措施就是在Jdk1.2后,引入了findclass方法,推荐用户去重写该方法,而不是直接重写 Load class方法,这样呢就依然能够符合双亲委派模型。

这就是史上第一次的双亲委派模型被破坏了,像很多事情(*装)只有零次和N次,双亲委派模型第二次被破坏,是由于这个模型自身的缺陷导致,双亲委派能很好的解决了各个类加载器协作时基础类型的一致性问题,但是如果有基础类型要调用用户的代码,这又该怎么办呢?

第二次被破坏

比如说JDK想要提供操作数据库的功能,那么数据库有很多种,并且随着时间的推移将会出现各种品类的数据库,想要JDK需要针对不同的数据库和具体代码都一一实现,这不现实,jdk也不知道各种品类数据里具体的实现方式,那么比较合理的方式就是JDK提供一组规范和接口,各个不同数据库厂商按照这个接口去实现自己的类库。

好,那么问题就出现了。JDK代码包中的加载肯定是使用了上层的类加载器的情况,二具体实现是由第三方厂商来实现的,加载第三方厂商的类加载器肯定不是bootstrap classloader,但是当你去调用jdk中的接口时呢,接口的所在类必然会引起第三方类库的加载,这就不符合自下而上的委派加载顺序了。出现了了上层加载器调用下层类加载器的情况,就产生了双亲委派模型的破坏。

我们来看一个具体的例子,在这个DriverManager这个类的类加载器为null,我之前说类加载器为null就是Bootstrap Classloader,因为它不可以被Java程序调用(C,C++)编写,而Driver Manager内部加载了两个driver,他们的类加载器都是Application ClassLoader,这说明不是Bootstrap 进行加载的,而是委托了Application ClassLoader去加载来自第三方的类,这个其实就是Java的SPI。

SPI 怎么做呢

为了解决这个困境,Java的设计团队只好引入了一个不太优雅的设计:线程上下文类加载器 (Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContext-ClassLoader()方 法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。

有了线程上下文类加载器,程序就可以做一些不符合双亲委派模型的事情了。JNDI服务使用这个线程上下文类,加载器去加载所需的SPI服务代码,这是一种父类加载器去请求子类加载器完成类加载的行为,这种行为实际上是打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型的一般性原则,但也是无可奈何的事情。

Java中涉及SPI的加载基本上都采用这种方式来完成,例如JNDI、 JDBC、JCE、JAXB和JBI等。不过,当SPI的服务提供者多于一个的时候,代码就只能根据具体提供者的类型来硬编码判断,为了消除这种极不优雅的实现方式,在JDK 6时,JDK提供了java.util.ServiceLoader类,以META-INF/services中的配置信息,辅以责任链模式,这才算是给SPI的加 载提供了一种相对合理的解决方案。

Spring Boot 当中的SPI机制也是在JDK的机制之上去完成的,实现的思路是一样的,只不过会比JDK的实现更加优雅。

从源码探究双亲委派机制相关推荐

  1. 类加载器源码、双亲委派、自定义类加载器详解

    文章目录 jdk的类加载器 双亲委派 自定义类加载器 打破双亲委派 jdk的类加载器 查看一个类的类加载器: ClassLoader classLoader = boy.class.getClassL ...

  2. 详解JVM之双亲委派机制

    双亲委派机制 定义 如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回.只有父类加载 ...

  3. JVM-白话聊一聊JVM类加载和双亲委派机制源码解析

    文章目录 Java 执行代码的大致流程 类加载loadClass的步骤 类加载器和双亲委派机制 sun.misc.Launcher源码解析 Launcher实例化 Launcher 构造函数 双亲委派 ...

  4. 【JVM】双亲委派机制详解

    通过上篇文章我们学习了类加载,也提到了因为双亲委派机制的存在自定义类加载器的实现,不要去覆盖ClassIoader类的loadClass方法,去实现findClass方法,接下来详细解释一下双亲委派机 ...

  5. java类加载机制为什么双亲委派_[五]类加载机制双亲委派机制 底层代码实现原理 源码分析 java类加载双亲委派机制是如何实现的...

    Launcher启动类 本文是双亲委派机制的源码分析部分,类加载机制中的双亲委派模型对于jvm的稳定运行是非常重要的不过源码其实比较简单,接下来简单介绍一下我们先从启动类说起有一个Launcher类 ...

  6. 双亲委派机制以及打破双亲委派机制

    双亲委派机制以及打破双亲委派机制 双亲委派机制 Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存中生成class对象,而且加载某个类的c ...

  7. JVM - 彻底理解打破双亲委派机制

    文章目录 Pre 双亲委派 何为打破双亲委派 如何打破双亲委派 演示 重写 ClassLoader#loadClass 失败原因探究 临时解决办法 验证是否成功 应用下新建Boss1类 自定义加载路径 ...

  8. 2021-03-19Tomcat源码学习--WebAppClassLoader类加载机制

    Tomcat源码学习--WebAppClassLoader类加载机制 在WebappClassLoaderBase中重写了ClassLoader的loadClass方法,在这个实现方法中我们可以一窥t ...

  9. 【Java 虚拟机原理】Android 类加载机制 ( 双亲委派机制 | BootClassLoader | PathClassLoader | DexClassLoader )

    文章目录 一.Android 类加载机制 二.双亲委派机制 一.Android 类加载机制 Android 中的类加载 使用了 双亲委派 机制 , 如下图所示 : 在 Android 中提供了 333 ...

最新文章

  1. Serverless无服务应用架构纵横谈
  2. iPhone开发四剑客之《iPhone开发秘籍》
  3. PHP isset 函数作用
  4. 【数理知识】《数值分析》李庆扬老师-第7章-非线性方程与方程组的数值解法
  5. ubuntu 安装 docky
  6. 这 56 个代码注释让我笑吐了
  7. oracle distinct用法
  8. HVV之WIFI蜜罐反制红队
  9. linux chattr修改文件属性,linux chattr(改变文件属性)
  10. AMD将于年内推出高端Polaris图形处理器
  11. 如何提取明细表头_BIM/Revit常用技巧——如何添加项目参数
  12. 中文版Postman测试需要登陆才能访问的接口(基于Cookie)
  13. CSS3实现轮播图效果
  14. 下载——百度文库下载方法
  15. linux oa系统搭建,企业Linux系统部署OA系统上线实例
  16. FPGA数字时钟计数器
  17. 23种设计模式python版
  18. 【论文】论文搜集+摘要翻译
  19. 人工智能就业前景如何?
  20. 浏览器被强制修改成 桔梗网—Google, Firefox

热门文章

  1. ABAP Report一般格式
  2. 修改deepin键位映射
  3. iPhone添加163邮箱,提示用户名或密码错误
  4. fpga期末考题库_FPGA系统设计答案试题题目及答案,期末考试题库,章节测验答案...
  5. ListView 实现斑马线颜色间隔效果(ListViewItem颜色间隔显示)
  6. 记一次台式机安装centos7的问题
  7. 详解浏览器最大并发连接数
  8. windows 下frp服务启动_Windows 下 Frp 设置开机自动启动
  9. Python 学习第三讲作业 2020-12- 20
  10. ajax自动局部刷新,jQuery如何实现AJAX定时刷新局部页面