一文搞懂“什么是双亲委派”

文章目录

  • 双亲委派介绍
  • 类加载器介绍
  • 类加载流程
  • 验证
  • 自定义类加载器
  • 为什么要设计这种机制

提前声明:以下介绍都是基于jdk9之前版本的双亲委派机制,jdk9及之后版本双亲委派会有变化,不在本篇介绍。

双亲委派介绍

双亲委派机制(Parent Delegation Mechanism)是Java中的一种类加载机制。在Java中,类加载器负责加载类的字节码并创建对应的Class对象。双亲委派机制是指当一个类加载器收到类加载请求时,它会先将该请求委派给它的父类加载器去尝试加载。只有当父类加载器无法加载该类时,子类加载器才会尝试加载。

这种机制的设计目的是为了保证类的加载是有序的,避免重复加载同一个类。Java中的类加载器形成了一个层次结构,根加载器(Bootstrap ClassLoader)位于最顶层,它负责加载Java核心类库。其他加载器如扩展类加载器(Extension ClassLoader)和应用程序类加载器(Application ClassLoader)都有各自的加载范围和职责。通过双亲委派机制,可以确保类在被加载时,先从上层的加载器开始查找,逐级向下,直到找到所需的类或者无法找到为止。

这种机制的好处是可以避免类的重复加载,提高了类加载的效率和安全性。同时,它也为Java提供了一种扩展机制,允许开发人员自定义类加载器,实现特定的加载策略。

类加载器介绍

上面说到了几个类加载器,小伙伴们可能有点懵,我简单介绍下3个最重要的类加载器。

  • Bootstrap classLoader:这个加载器是最顶层的加载器,主要加载核心类库,%JRE_HOME%\lib下的jar包,在jdk源码中其实是找不到这个类的,它是使用C/C++语言实现的,本身是虚拟机的一部分。不过能够在ClassLoader类中调用findBootstrapClass(String name)方法取得这个类,不过该方法也是native方法,看不到具体实现。
  • ExtClassLoader:这个是扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包。需要注意的是Bootstrap classLoader并不是ExtClassLoader严格意义上的父加载器,ExtClassLoader实例中有个变量是ClassLoader parent,这个变量的值是null。当ClassLoader执行loadClass(String name, boolean resolve)方法时,检查到parent值是null,然后会执行findBootstrapClassOrNull(String name)找到Bootstrap classLoader
  • AppClassLoader:这个类加载当前应用的classpath的所有类。它的ClassLoader parentExtClassLoaderAppClassLoaderExtClassLoader最顶层的父类是ClassLoaderClassLoader是个抽象类。

类加载流程

类的加载流程用一句话简单说就是向上委托,向下查找加载。流程如下:
在这里插入图片描述
下面再用代码看一下类加载器的加载流程。

public abstract class ClassLoader {protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}
}

这段代码基本上解释了前面说的类加载流程。

  1. 先是执行findLoadedClass(String)去检测这个class是不是已经加载过了。
  2. 执行父加载器的loadClass方法。如果父加载器为null,则jvm内置的加载器去替代,也就是Bootstrap ClassLoader。这也解释了ExtClassLoaderparent为null,但仍然说Bootstrap ClassLoader是它的父加载器,这里最终加载方法是一个native方法。
  3. 如果向上委托父加载器没有加载成功,则通过findClass(String)查找。

验证

上面讲了类加载的流程,那我们就写代码验证一下。

public class ClassLoaderExample {public static void main(String[] args) {//当前类加载器ClassLoader loader = ClassLoaderExample.class.getClassLoader();System.out.println(loader);//父类加载器ClassLoader parentLoader = loader.getParent();System.out.println(parentLoader);//父类的父类ClassLoader grandParentLoader = parentLoader.getParent();System.out.println(grandParentLoader);}
}

结果如下:

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1540e19d
null

可以看出我们自定义的类确实是由AppClassLoader加载的,而AppClassLoader的父加载器确实是ExtClassLoader,而ExtClassLoader的父加载器也确实是null。

自定义类加载器

Java提供了抽象类java.lang.ClassLoader,所有用户自定义的类加载器都应该继承ClassLoader类。

在自定义ClassLoader的子类时候,我们常见的做法是:

  1. 重写loadClass(String name)方法;
  2. 重写findclass(String name)方法;

子类重写loadClass()方法的实现最好是直接调用super.loadClass()方法,因为这个方法是实现双亲委派模型逻辑的地方,擅自修改这个方法的逻辑会导致模型被破坏,容易造成问题。当调用super.loadClass()没有加载到类时,再调用findclass()方法。

而重写findclass()方法就没那么多顾虑了,这个方法的实现就是在某个路径下发现并加载对应的类。

具体实现代码如下:

package org.example;import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;public class MyClassLoader extends ClassLoader {private String path;private String packageName;public MyClassLoader(String path, String packageName) {this.path = path;this.packageName = packageName;}@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {// 首先尝试使用父类加载器加载类try {return super.loadClass(name);} catch (ClassNotFoundException e) {// 如果父类加载器无法加载类,则自己加载类return findClass(name);}}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {// 在这里实现自己的类加载逻辑,例如从特定位置加载类文件// 这里只是一个示例,实际实现需要根据具体需求进行处理byte[] classData = loadClassData(name);if (classData == null) {throw new ClassNotFoundException();}return defineClass(packageName + name, classData, 0, classData.length);}private byte[] loadClassData(String name) {// 在这里实现加载类文件的逻辑,返回类文件的字节数组// 这里只是一个示例,实际实现需要根据具体需求进行处理try {FileInputStream fis = new FileInputStream(path + name + ".class");ByteArrayOutputStream bos = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int len;while ((len = fis.read(buffer)) != -1) {bos.write(buffer, 0, len);}fis.close();bos.close();return bos.toByteArray();} catch (IOException e) {e.printStackTrace();}return null;}public static void main(String[] args) {// 创建自定义类加载器MyClassLoader classLoader = new MyClassLoader("/Users/bigfish/IdeaProjects/basic/target/classes/org/example/", "org.example.");try {// 使用自定义类加载器Class<?> clazz = classLoader.loadClass("MyClassLoader");System.out.println("加载此类的类加载器为:" + clazz.getClassLoader());System.out.println("加载此类的类加载器的父类加载器为:" + clazz.getClassLoader().getParent());} catch (ClassNotFoundException e) {System.out.println("Class not found");}}}

结果如下:

加载此类的类加载器为:org.example.MyClassLoader@1540e19d
加载此类的类加载器的父类加载器为:sun.misc.Launcher$AppClassLoader@18b4aac2

可以看出我们自定义的类加载的父类加载器为AppClassLoader,各个类加载器的调用顺序如下:

在这里插入图片描述
看到这里,也许有人会疑惑,上面代码中我们并没有把AppClassLoader作为父类加载器给自定义类加载器赋值啊,那为何父类加载器会是AppClassLoader呢?

其实是有赋值的地方的,是在ClassLoader的无参构造方法赋值的,代码如下。

protected ClassLoader(ClassLoader parent) {this(checkCreateClassLoader(), parent);
}protected ClassLoader() {this(checkCreateClassLoader(), getSystemClassLoader());
}

其中getSystemClassLoader()返回的就是AppClassLoader

为什么要设计这种机制

最后总结一下,这种设计有个好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。


  • 个人公众号
    个人公众号
  • 个人小游戏
    个人小游戏

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/257597.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端秘法进阶篇之事件循环

目录 一.浏览器的进程模型 1.进程 2.线程 二.浏览器的进程和线程 1. 浏览器进程 2. 网络进程 3. 渲染进程 三.渲染主线程 四.异步 五.优先级 1. 延时队列&#xff1a; 2.交互队列&#xff1a; 3.微队列&#xff1a; 六.JS 的事件循环 附加:JS 中的计时器能做到精…

MATLAB实现LSTM时间序列预测

LSTM模型可以在一定程度上学习和预测非平稳的时间序列&#xff0c;其具有强大的记忆和非线性建模能力&#xff0c;可以捕捉到时间序列中的复杂模式和趋势[4]。在这种情况下&#xff0c;LSTM模型可能会自动学习到时间序列的非平稳性&#xff0c;并在预测中进行适当的调整。其作为…

微信小程序介绍、账号申请、开发者工具目录结构详解及小程序配置

目录 一、微信小程序介绍 1.什么是小程序&#xff1f; 2.小程序可以干什么&#xff1f; 3.微信小程序特点 二、账号申请 1.账号注册 2.测试号申请 三、安装开发工具 四、开发小程序 五、目录结构 JSON 配置 小程序配置 app.json 工具配置 project.config.json 页…

云备份项目:在云端保护您的数据【二、开发】

☘️过度的信息对一个过着充实生活的人来说&#xff0c;是一种不必要的负担☘️ 文章目录 前言工具类实现文件实用工具类代码实现 Json实用工具类代码实现 服务端单例配置类系统配置信息单例配置类 数据管理类数据信息数据管理 热点管理类业务处理类 客户端数据管理类文件备份类…

高级FPGA开发之基础协议PCIe(二)

高级FPGA开发之基础协议之PCIe&#xff08;二&#xff09; 一、TLP报文类型 在PCIe总线中&#xff0c;存储器读写、I/O读写和配置读写请求TLP主要由以下几类报文组成&#xff1a; 1.1 存储器读请求TLP和读完成TLP 当PCIe主设备&#xff08;RC或者EP&#xff09;访问目标设备…

DS:二叉树的链式结构及实现

创作不易&#xff0c;友友们给个三连吧&#xff01;&#xff01; 一、前言 前期我们解释过二叉树的顺序结构&#xff08;堆&#xff09;为什么比较适用于完全二叉树&#xff0c;因为如果用数组来实现非完全二叉树&#xff0c;那么数组的中间部分就可能会存在大量的空间浪费。 …

【MySQL】高度为2和3时B+树能够存储的记录数量的计算过程

文章目录 题目答案高度为2时的B树高度为3时的B树总结 GPT4 对话过程 题目 InnoDB主键索引的Btree在高度分别为 2 和 3 时&#xff0c;可以存储多少条记录&#xff1f; 答案 高度为2时的B树 计算过程&#xff1a; 使用公式 ( n 8 ( n 1 ) 6 16 1024 ) (n \times 8 …

STM32F1 引脚重映射功能

STM32 端口引脚重映射 文章目录 STM32 端口引脚重映射前言1、查阅芯片数据手册1.1 串口引脚重映射描述 2、代码部分2.1 核心代码部分 3、实验现象4、总结 前言 在写程序时遇到想要的端口功能&#xff0c;而这个引脚又被其它的功能占用了无法删除掉或直接使用&#xff0c;这种情…

实例分割论文阅读之:《Mask Transfiner for High-Quality Instance Segmentation》

1.摘要 两阶段和基于查询的实例分割方法取得了显著的效果。然而&#xff0c;它们的分段掩模仍然非常粗糙。在本文中&#xff0c;我们提出了一种高质量和高效的实例分割Mask Transfiner。我们的Mask Transfiner不是在规则的密集张量上操作&#xff0c;而是将图像区域分解并表示…

深入探索Pandas读写XML文件的完整指南与实战read_xml、to_xml【第79篇—读写XML文件】

深入探索Pandas读写XML文件的完整指南与实战read_xml、to_xml XML&#xff08;eXtensible Markup Language&#xff09;是一种常见的数据交换格式&#xff0c;广泛应用于各种应用程序和领域。在数据处理中&#xff0c;Pandas是一个强大的工具&#xff0c;它提供了read_xml和to…

蓝桥杯嵌入式学习记录——按键的使用

目录 一、按键原理简介 二、cubeMX的配置 三、按键的短按代码 四、按键的长按代码 一、按键原理简介 在STM32中&#xff0c;按键连接通常使用GPIO&#xff08;通用输入/输出&#xff09;端口来实现。当按键未被按下时&#xff0c;GPIO端口处于高电平状态&#xff08;即1&am…

Linux第48步_编译正点原子的出厂Linux内核源码

编译正点原子的出厂 Linux 内核源码&#xff0c;为后面移植linux做准备。研究对象如下&#xff1a; 1)、linux内核镜像文件“uImage” 路径为“arch/arm/boot”&#xff1b; 2)、设备树文件“stm32mp157d-atk.dtb” 路径为“arch/arm/boot/dts” 3)、默认配置文件“stm32m…

3D高斯溅射:面向三维场景的实时渲染技术

1. 前言 高斯溅射技术【1】一经推出&#xff0c;立刻引起学术界和工业界的广泛关注。相比传统的隐式神经散射场渲染技术&#xff0c;高斯溅射依托椭球空间&#xff0c;显性地表示多目图像的三维空间关系&#xff0c;其计算效率和综合性能均有较大的提升&#xff0c;且更容易理…

【AI视野·今日NLP 自然语言处理论文速览 第七十八期】Wed, 17 Jan 2024

AI视野今日CS.NLP 自然语言处理论文速览 Wed, 17 Jan 2024 (showing first 100 of 163 entries) Totally 100 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computation and Language Papers Deductive Closure Training of Language Models for Coherence, Accur…

VueCLI核心知识3:全局事件总线、消息订阅与发布

这两种方式都可以实现任意两个组件之间的通信 1 全局事件总线 1.安装全局事件总线 import Vue from vue import App from ./App.vueVue.config.productionTip false/* 1.第一种写法 */ // const Demo Vue.extend({}) // const d new Demo()// Vue.prototype.x d // 把Dem…

XML学习

XML学习 1 XML介绍2 XML语法3 XML特殊字符4 XML文档结构5 XML命名空间 1 XML介绍 XML是可扩展的标记性语言&#xff0c;常用来传输和存储数据。可见于Web中的web.xml和Maven中的pom.xml version是版本&#xff0c;encoding是编码 <?xml version"1.0" encodin…

IMX6ULL移植U-Boot 2022.04

目录 目录 1.编译环境以及uboot版本 2.默认编译测试 3.uboot中新增自己的开发板 3.编译测试 4.烧录测试 5.patch文件 1.编译环境以及uboot版本 宿主机Debian12u-boot版本lf_v2022.04 ; git 连接GitHub - nxp-imx/uboot-imx: i.MX U-Boot交叉编译工具gcc-arm-10.3-2021.0…

【PyQt】08 - 编辑Tab顺序

文章目录 前言一、Tab顺序二、编辑Tab顺序总结 前言 介绍了什么是Tab顺序&#xff0c;以及如何修改Tab顺序。 一、Tab顺序 当你的界面设计好之后&#xff0c;在输入栏按住Tab按键&#xff0c;他会按照你摆放的顺序一次转跳 二、编辑Tab顺序 方法一 然后鼠标左击就可以改变…

PHP毕业设计图片分享网站76t17

图片分享网站主要是为了提高工作人员的工作效率和更方便快捷的满足用户&#xff0c;更好存储所有数据信息及快速方便的检索功能&#xff0c;对系统的各个模块是通过许多今天的发达系统做出合理的分析来确定考虑用户的可操作性&#xff0c;遵循开发的系统优化的原则&#xff0c;…

Vegeta压测工具学习与使用

Vegeta压测工具学习与使用 目标&#xff1a; 能够在命令行下使用Vegeta对指定API进行测试了解如何导出结果&#xff0c;以及能获得什么样的结果(P99,P99.9,QPS)探索能否导出其他结果&#xff0c;是否能够执行复杂命令或简易脚本等 时间比较紧迫&#xff0c;预计两到三个小时内完…