【JVM】什么是双亲委派机制

文章目录

    • 1、类加载机制
    • 2、双亲委派模型
      • 2.1、介绍
      • 2.2、为什么需要双亲委派
      • 2.3、源码解析
    • 3、破坏双亲委派
      • 3.1、介绍
      • 3.2、破坏实现
      • 3.3、破坏双亲委派的例子
    • 4、线程上下文类加载器

1、类加载机制

类加载阶段分为加载、连接、初始化三个阶段,而加载阶段需要通过类的全限定名来获取定义了此类的二进制字节流。Java特意把这一步抽出来用类加载器来实现。把这一步骤抽离出来使得应用程序可以按需自定义类加载器。并且得益于类加载器,OSGI、热部署等领域才得以在JAVA中得到应用。

image-20230314151143595

这一部分可参考之前的文章《【JVM】搞清类加载机制》。

在Java中任意一个类都是由这个类本身和加载这个类的类加载器来确定这个类在JVM中的唯一性。也就是你用A类加载器加载的com.aa.ClassA和B类加载器加载的com.aa.ClassA它们是不同的,也就是用instanceof这种对比都是不同的,所以即使都来自于同一个class文件但是由不同类加载器加载的那就是两个独立的类。

站在Java虚拟机的角度来看,只存在两种不同的类加载器,分别是启动类加载器和其它所有的类加载器。其中启动类加载器使用C++语言实现,是虚拟机自身的一部分;其它所有的类加载器则都是由Java语言实现,是独立于虚拟机外部,并且全部都继承自抽象类java.lang.ClassLoader

站在Java开发人员的角度来看,类加载器就应该划分得更细致一些。自JDK1.2以来,Java一直保持着三层类加载器、双亲委派的类加载结构。其中JDK提供的三层类加载器如下:

  1. 启动类加载器(Boostrap Class Loader):C++语言实现,是虚拟机自身的一部分,负责加载存放在<JAVA_HOME>\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够识别的。启动类加载器无法被Java程序直接引用,在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器时,直接使用null代替即可;

注意的设计,Java虚拟机是按照文件名识别的,如rt.jar、tools.jar,名字不符合的类库及时放在lib目录中也不会被加载。

  1. 扩展类加载器(Extension Class Loader):独立于虚拟机外部,于类sun.misc.launcher$ExtClassLoader中以Java代码的形式实现,负责加载<JAVA_HOME>\lib\ext目录,或者被java.ext.dirs系统变量所指定的路径中所有的类库;

  2. 应用程序类加载器(Application Class Loader):独立于虚拟机外部,由sun.misc.launcher$AppClassLoaderJava代码的形式实现,负责加载**用户类路径(ClassPath)**上所有的类库,在开发过程中可直接使用这个类加载器。如果应用程序中没有自定义过自己的类加载器,一般情况下就是这个程序中默认的类加载器。

2、双亲委派模型

2.1、介绍

介绍完JDK中的三种类加载器,那么就不得不说一下双亲委派模型了。

JDK9之前的Java应用都是由上述三种类加载器互相配合来完成加载的,如果觉得有必要,还可自定义类加载器来对功能进行拓展。

双亲委派模型(Parents Delegation Medel)是指各种类加载器之间的层级关系。该模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。同时这里类加载器之间的父子关系一般是通过组合关系来复用父加载器的代码而非继承。

双亲委派模型

双亲委派模型核心的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传递到最顶层的启动类加载器中。只有当父加载器反馈自己无法完成这个加载请求,即搜索范围内没有找到所需要的类时,子加载器才会尝试自己去完成加载。

双亲委派的核心流程

2.2、为什么需要双亲委派

双亲委派保证类加载器,自下而上的委派,又自上而下的加载,保证每一个类在各个类加载器中都是同一个类

使用双亲委派模型来组织类加载器之前的关系,一个显而易见的好处就是Java中的类随着它的类加载器一起具备了一种带有优先级的层级关系

一个非常明显的目的就是保证Java官方的类库<JAVA_HOME>\lib和扩展类库<JAVA_HOME>\lib\ext的加载安全性,不会被开发者覆盖。

例如类java.lang.Object,它存放在rt.jar之中,无论哪个类加载器要加载这个类,最终都是委派给启动类加载器加载,从而保证Object类在程序的各种类加载器环境中从始至终都是同一个类。

如果我们自己开发开源框架,也可以自定义类加载器,利用双亲委派模型,保护自己框架需要加载的类不被应用程序覆盖。

2.3、源码解析

双亲委派模型对于保证Java程序的稳定运作极为重要,但它的实现却异常简单,用以实现双亲委派的代码只有短短十余行,全部集中在java.lang.ClassLoaderloadClass()方法之中:

public abstract class ClassLoader {//每个类加载器都有个父加载器private final ClassLoader parent;/*** 双亲委派核心实现*/public Class<?> loadClass(String name) {//查找一下这个类是不是已经加载过了Class<?> c = findLoadedClass(name);//如果没有加载过if( c == null ){//先委派给父加载器去加载,注意这是个递归调用if (parent != null) {c = parent.loadClass(name);}else {// 如果父加载器为空,查找Bootstrap加载器是不是加载过了c = findBootstrapClassOrNull(name);}}// 如果父加载器没加载成功,调用自己的findClass去加载if (c == null) {c = findClass(name);}return c;}protected Class<?> findClass(String name){//1. 根据传入的类名name,到在特定目录下去寻找类文件,把.class文件读入内存...//2. 调用defineClass将字节数组转成Class对象return defineClass(buf, off, len)}// 将字节码数组解析成一个Class对象,用native方法实现protected final Class<?> defineClass(byte[] b, int off, int len){...}
}

从上面的代码可以得到几个关键信息:

  • JVM 的类加载器是分层次的,它们有父子关系,而这个关系不是继承维护,而是组合,每个类加载器都持有一个 parent 字段,指向父加载器。

AppClassLoaderparentExtClassLoaderExtClassLoaderparentBootstrapClassLoader,但是BootstrapClassLoaderparent=null

  • defineClass 方法的职责是调用 native 方法把 Java 类的字节码解析成一个 Class 对象。

  • findClass 方法的主要职责就是找到.class文件并把.class文件读到内存得到字节码数组,然后调用 defineClass 方法得到 Class 对象。子类必须实现findClass

  • loadClass 方法的主要职责就是实现双亲委派机制:首先检查这个类是否已经被加载过了,如果加载过了直接返回,否则委派给父加载器加载,这是一个递归调用,一层一层向上委派,最顶层的类加载器(启动类加载器)无法加载该类时,再一层一层向下委派给子类加载器加载。

双亲委派的核心流程

3、破坏双亲委派

3.1、介绍

双亲委派模型对于保证Java程序的稳定运作极为重要,但它并不是一个具有强制性约束力的模型,而是Java设计者推荐给开发者们的类加载器实现方式,这就代表着双亲委派机制是可以被破坏的。

3.2、破坏实现

前面提及到我们是可以自定义类加载器的,实现的方法也很简单,继承ClassLoader类并重写相关的方法即可。而当我们想要破坏双亲委派计智时,我们也需要自定义一个类加载器,通过实现自己的类加载逻辑打破原有的加载顺序。

在这里对ClassLoader类的三个核心方法进行介绍,在前面的源码解析中也有提及:

  • loadClass():主要进行类加载的方法,默认的双亲委派机制就实现在这个方法中;
  • findClass():根据名称或位置加载.class字节码;
  • definclass():把字节码转化为Class。

接下来将举一个简单的例子,当只有加载String时才会委派给父加载器进行加载,否则通过自定义的类加载逻辑进行加载,从而破坏双亲委派机制。具体实现如下:

public class MyClassLoader extends ClassLoader {@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {if (!"java.lang.String".equals(name)) {// 如果类名不是java.lang.String,直接加载类return findClass(name);} else {// 如果类名是java.lang.String,委派给父ClassLoader加载return super.loadClass(name);}}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {// 实现自己的类加载逻辑// ...}
}

需要注意的是,破坏双亲委派机制可能会导致类加载的混乱和不稳定性,不推荐在正式的应用程序中使用。双亲委派机制的目的是为了保证类的加载是有序的、稳定的,能够避免类的重复加载和冲突。在一些特殊的情况下,可能需要破坏双亲委派机制来实现一些特定的需求,但在大多数情况下,最好遵循双亲委派机制的原则。

3.3、破坏双亲委派的例子

​ 破坏双亲委派的例子有许多,最容易想到的便是自己搞破坏了,还有许多我们是不知道的,如双亲委派机制历史中的三次较大规模“被破坏”的情况、JDBC、OSGI等模块化技术、Tomcat等。

这里简单说一下Tomcat是如何破坏双亲委派机制的。

在Tomcat中,每个Web应用程序都有自己的类加载器,称为Web应用程序类加载器(WebAppClassLoader)。当Tomcat启动时,会为每个Web应用程序创建一个独立的WebAppClassLoader,它负责加载该Web应用程序的类。

为了保证每个Web项目互相独立,所以不能都由AppClassLoader加载,因此自定义了类加载器WebappClassLoaderWebappClassLoader继承URLClassLoader重写findClass和loadClass,并且WebappClassLoader父类加载器设置为AppClassLoader

WebappClassLoader加载过程如下:

  1. WebappClassLoader.loadClass中会先在缓存中查看类是否加载过;
  2. 如果已经加载过,直接返回;
  3. 如果没有加载过,就交给ExtClassLoaderExtClassLoader再交给BootstrapClassLoader加载;
  4. 如果都加载不了,才自己加载;
  5. 如果自己也加载不了,就遵循原始的双亲委派,交由AppClassLoader递归加载。

4、线程上下文类加载器

线程上下文类加载器其实是一种类加载器传递机制。可以通过java.lang.Thread#setContextClassLoader方法给一个线程设置上下文类加载器,在该线程后续执行过程中就能通过java.lang.Thread#getContextClassLoader把这个类加载器取出来使用。

如果创建线程时未设置上下文类加载器,将会从父线程(parent = currentThread())中获取,如果在应用程序的全局范围内都没有设置过,就默认是应用程序类加载器

一个典型的例子便是JNDI服务,JNDI现在已经是Java的标准服务,它的代码由启动类加载器去加载(在JDK 1.3时放进去的rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者(SPI,Service Provider Interface)的代码,但启动类加载器不可能去加载ClassPath下的类。

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

Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。

参考:

  1. 《深入理解Java虚拟机-JVM高级特性与实践》
  2. 你确定你真的理解"双亲委派"了吗?
  3. 《深入拆解Tomcat & Jetty》——Tomcat如何打破双亲委托机制?
  4. 【JVM】搞清类加载机制

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

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

相关文章

Vue2(初识vue)

目录 一&#xff0c;Vue2简介1.1&#xff0c;什么是vue1.2&#xff0c;初始vue1.3&#xff0c;搭建vue环境1.4&#xff0c;第一个hello world 二&#xff0c;基础知识2.1 指令2.2-1 指令v-text2.2-2 指令v-html2.2-3 指令v-if2.2-4 指令v-else2.2-5 指令v-show2.2-6 v-if指令与…

深入学习 Redis - 渐进式遍历 scan 命令、数据库管理命令

目录 前言 一、scan 命令 二、数据库管理命令 select dbsize flushdb / flushall 前言 之前我们所了解到的 keys * 是一次性把整个 redis 中所有的 key 都获取到&#xff0c;但是整个操作比较危险&#xff0c;可能会一下子的都太多的 key&#xff0c;阻塞 redis 服务器. …

centos系统离线安装k8s v1.23.9最后一个版本并部署服务,docker支持的最后一个版本

注意&#xff1a;我这里的离线安装包是V1.23.9. K8S v1.23.9离线安装包下载&#xff1a; 链接&#xff1a;https://download.csdn.net/download/qq_14910065/88143546 这里包括离线安装所有的镜像&#xff0c;kubeadm&#xff0c;kubelet 和kubectl&#xff0c;calico.yaml&am…

linux系统共享文件夹的创建和使用(VMware )

虚拟机设置共享文件夹 点击设置 点击选项 选择共享文件夹 随便添加一个电脑上的文件夹 虚拟机内打开共享文件夹 打开根目录 打开mnt文件夹 继续点击 最终得到共享文件夹

一文讲清多线程与多线程同步

1 多线程 1.1 线程的概念 十多年前&#xff0c;主流观点主张在可能的情况下优先选择多进程而非多线程&#xff0c;如今&#xff0c;多线程编程已经成为编程领域的事实标准。多线程技术在很大程度上改善了程序的性能和响应能力&#xff0c;使其能够更加高效地利用系统资源&…

【读书笔记】CHAPTER 1: SCALE FROM ZERO TO MILLIONS OF USERS

如果你有一定工作经验&#xff0c;或者开发项目基础&#xff0c;就看这个图就可以。 一个“从零扩展到数百万用户”系统&#xff0c;基本由上述部分组成&#xff1a; web、app&#xff1a;触达用户的设备&#xff08;页面&#xff09; DNS&#xff1a;用户看到的都是域名(ww…

count(列名) ,count(1)与count(*) 有何区别?

Mysql版本&#xff1a;8.0.26 可视化客户端&#xff1a;sql yog 文章目录 一、Mysql之count函数简介二、count(列名) &#xff0c;count(常量)与count(*) 有何区别&#xff1f;2.1 统计字段上的区别2.2 执行效率上的区别 一、Mysql之count函数简介 &#x1f449;表达式 COUNT(…

Centos7 上安装 redis-dump 和redis-load 命令

一、安装rvm 1、安装GPG keys gpg2 --keyserver keyserver.ubuntu.com --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDBcurl -sSL http://rvm.io/mpapis.asc | gpg2 --import - curl -sSL http://rvm.io/pkuczynski.asc | g…

如何在项目需求与技术方案未确定的情况下掌控上线时间?

需求不明确与技术方案未确定的挑战 在任何项目管理过程中&#xff0c;需求和技术方案是两个核心环节。理想情况下&#xff0c;我们希望在项目开始阶段就有清晰明确的需求和经过深思熟虑的技术方案。然而&#xff0c;现实中的项目管理往往并不如此理想。 项目需求的重要性 需求…

谷粒商城第七天-商品服务之分类管理下的删除、新增以及修改商品分类

目录 一、总述 1.1 前端思路 1.2 后端思路 二、前端部分 2.1 删除功能 2.2 新增功能 2.3 修改功能 三、后端部分 3.1 删除接口 3.2 新增接口 3.3 修改接口 四、总结 一、总述 1.1 前端思路 删除和新增以及修改的前端无非就是点击按钮&#xff0c;就向后端发送请求…

将网盘挂载到本地保姆级教程

视频链接&#xff1a;https://www.bilibili.com/video/BV1SA411B7qc Clouddrive2&#xff1a;是一款非常实用的国内网盘服务挂载应用。它支持多种国内知名网盘服务&#xff0c;包括阿里云盘、115云盘、天翼云盘等。通过CloudDrive 2&#xff0c;你可以将这些网盘服务挂载到你的…

夏日暴雨,6大安全应对攻略,让我们一起做好防范

夏季暴雨来袭&#xff0c;我们必须高度警惕&#xff01;短时间的强降雨可能导致积水、山区滑坡、城市内涝等问题&#xff0c;给社会和经济发展带来严重影响。今天&#xff0c;让我们一起探讨一些有效的防范措施&#xff0c;确保个人安全与减少灾害损失。关注天气预警&#xff0…

MyBatis查询数据库

1.MyBatis 是什么&#xff1f; MyBatis 是⼀款优秀的持久层框架&#xff0c;它⽀持⾃定义 SQL、存储过程以及⾼级映射。MyBatis 去除了几乎所有的 JDBC 代码以及设置参数和获取结果集的⼯作。MyBatis 可以通过简单的 XML 或注解来配置 和映射原始类型、接⼝和 Java POJO&#…

groovy.lang.GroovyRuntimeException:Ambiguous method

目录 问题 根因及解决 问题 今天线上的计算脚本报了个问题&#xff0c;报警如下是 groovy 运行时异常。调用方法 org.springframework.util.CollectionUtils#isEmpty 出现了异常。异常原因是由于方法重载造成了歧义。无法确定如何调用方法 [null]&#xff0c;因为它同时匹配了…

UE4/5 PoseDriver 动画蓝图节点使用

PoseDriver节点可以进行Pose的比对&#xff0c;从而针对不同Pose生成不同权重数值&#xff0c;权重数值可应用至MorphTarget上使动画更加逼真&#xff0c;或应用至角色挂件上&#xff0c;制作出类惯性或弹簧的附加效果。 1.创建Pose 这里创建Box作为演示&#xff0c;下图大Bo…

《评论文章-无线纳米技术可以降低脊髓刺激成本和并发症,传统设备与无线刺激设备费用相比的回顾》

SCS治疗可能会出现并发症&#xff0c;并且管理这些并发症的费用很高。 慢性疼痛是促使人们寻求缓解的主要因素&#xff0c;也是阿片类药物研究的主要方向。 SCS治疗取得了突破性进展&#xff0c;在治疗背部手术失败综合征、神经性疼痛障碍、复杂区域疼痛综合征以及血管缺血引…

【数据库】将excel数据导入mysql数据库

环境&#xff1a;Windows10 mysql8以上 将你要导入的excel表另存为txt格式 打开txt格式文件&#xff0c;删除表头行并另存为并更改编码方式&#xff08;由于与数据库的编码不同&#xff0c;会导致导入报错&#xff09; 通过命令行登录数据库 winr cmd进入 进入装mysql的目录位…

Kafka3.0.0版本——Broker(总体工作流程)

目录 一、Kafka中Broker总体工作流程图解二、Kafka中Broker总体工作流程步骤解析 一、Kafka中Broker总体工作流程图解 总体工作流程图解 二、Kafka中Broker总体工作流程步骤解析 1、broker启动后在zk中注册&#xff0c;如下图所示&#xff1a; 2、controller谁先注册&…

【leetcode经典简单题】自食用||删除链表中倒数第k个结点

step by step. 题目&#xff1a; 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5]示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&a…

怎样在Apipost中设计出实用又好看的API文档

Apipost一直推荐文档先行的API设计理念&#xff0c;在Apipost中可以添加Markdown格式的文本&#xff0c;用以储备文档和API文档设计。 作为一种轻量级标记语言&#xff0c;Markdown在撰写文档、博客文章、README文件以及网站内容上被广泛使用。 如何在Apipost中设计出漂亮的文…