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

news2025/4/21 13:44:56

文章目录

    • 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 {
    @Override
    public 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);
        }
    }

    @Override
    protected 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.coloradmin.cn/o/820548.html

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

相关文章

【Ansible】Ansible自动化运维工具之playbook剧本搭建LNMP架构

LNMP 一、playbooks 分布式部署 LNMP1. 环境配置2. 安装 ansble3. 安装 nginx3.1 准备 nginx 相关文件3.2 编写 lnmp.yaml 的 nginx 部分3.3 测试 nginx4. 安装 mysql4.1 准备 mysql 相关文件4.2 编写 lnmp.yaml 的 mysql 部分4.3 测试 mysql5. 安装 php5.1 编写 lnmp.yaml 的 …

健身时戴什么耳机比较好、健身用的运动耳机推荐

运动健身已经成为一种潮流&#xff0c;有的人为了追求马甲线和八大块腹肌&#xff0c;还有的人为了缓解学习和工作的压力。但你在运动健身的时候难免会有烦躁、疲惫的时候&#xff0c;如果这时有音乐的加入那就完美了&#xff0c;因为美妙的歌声能冲淡这种疲惫感&#xff0c;让…

面向金融科技方向选手!一级学会背书,AI选股与可视分析大赛来啦

金融量化领域邂逅人工智能&#xff0c;将会迸发出怎样的火花&#xff1f; 在深度学习、强化学习和自然语言处理等技术取得不断突破和创新的今天&#xff0c;AI如何赋能量化投资领域&#xff0c;助力开发者打造表现优异&#xff0c;更加安全可靠的量化模型&#xff1f; 第四届CS…

回收站清空的文件怎么恢复?文件恢复,就靠这3个方法!

“不小心把回收站清空了怎么办&#xff1f;之前在回收站里的文件还能恢复吗&#xff1f;诚心发问&#xff0c;希望大家给我出出主意。” 文件被删除之后通常在电脑的回收站中还可以还原。但实际使用电脑时&#xff0c;很多朋友为了释放电脑内存&#xff0c;都会有定期清空回收站…

【Redis】内存数据库 Redis 进阶

目录 分布式缓存 RedisRedis 持久化RDB (Redis DataBase)RDB执行时机RDB启动方式——save指令save指令相关配置save指令工作原理save配置自动执行 RDB启动方式——bgsave指令bgsave指令相关配置bgsave指令工作原理 RDB三种启动方式对比RDB特殊启动形式RDB优点与缺点 AOF (Appen…

租赁固定资产管理

智能租赁资产管理系统可以为企业单位提供RFID资产管理系统。移动APP资产管理&#xff0c;准确总结易损耗品和固定资金&#xff0c;从入库到仓库库存实时跟踪&#xff0c;控制出库和入库的全过程。同时&#xff0c;备件和耗材与所属资产设备有关&#xff0c;便于备件的申请和管理…

指针进阶详解续---C语言

❤博主CSDN:啊苏要学习 ▶专栏分类&#xff1a;C语言◀ C语言的学习&#xff0c;是为我们今后学习其它语言打好基础&#xff0c;C生万物&#xff01; 开始我们的C语言之旅吧&#xff01;✈ 目录 前言&#xff1a; 一.函数指针数组 二.指向函数指针数组的指针 三.回调函数 …

3D WEB轻量化渲染引擎Communicator发布冲突检测库!增加客户端和服务器端冲突检测功能

​HOOPS Communicator是Tech Soft 3D旗下的主流产品之一&#xff0c;具有强大的、专用的高性能图形内核&#xff0c;专注于基于Web的高级3D工程应用程序。其由HOOPS Server和HOOPS Web Viewer两大部分组成&#xff0c;提供了HOOPS Convertrer、Data Authoring的模型转换和编辑工…

【Java练习题汇总】《第一行代码JAVA》综合测试二,汇总Java练习题

Java练习题 综合测试二 1️⃣ 综合测试二 1️⃣ 综合测试二 下面( )不属于面向对象的特点。 A. 封装 B. 转型 C. 继承 D. 多态 下面关于类与对象的描述正确的是( )。 A. 任何情况下必须先有类再有对象&#xff0c;对象只能够调用类中定义的方法&#xff0c;不能够调用属性 B.…

大家知道AI智能绘画是什么吗?我将为你解答

最近&#xff0c;我经常看到朋友在朋友圈上一直发美女图&#xff0c;我在想“这小子现在怎么回事&#xff0c;天天发女孩子的图片”。 便去询问了一下&#xff0c;他却笑着对我说&#xff1b;“这些都不是真人&#xff0c;是我ai智能绘画出图的”。 顿时&#xff0c;我就有点懵…

详细谈谈AIO、BIO、NIO 的区别

目录 一、什么是AIO 二、什么是BIO 三、什么是NIO 四、NIO 在 Netty 中的使用 一、什么是AIO AIO&#xff08;Asynchronous I/O&#xff0c;异步输入输出&#xff09;是一种处理输入输出的编程模型&#xff0c;它允许同时处理多个输入输出操作&#xff0c;而不需要等待每个…

Activity的自启动模式

以下内容摘自郭霖《第一行代码》第三版 文章目录 Activity的自启动模式1.standard&#xff08;默认&#xff09;2.singleTop3.singleTask4.singleInstance Activity的自启动模式 启动模式一共有4种&#xff0c;分别是standard、singleTop、singleTask和singleInstance&#x…

java: MultiValueMap

实际就是value为List的map.在较早版本(比如2.3.7)的spring boot 的loadSpringFacotories方法中有使用&#xff0c; 新版本(比如2.6.6&#xff09;已换成Map<String,List> MultiValueMap用法很简单&#xff1a;

资产盘点流程及注意事项

公司在引进固定资产管理的同时&#xff0c;也广泛加快了信息化工作的进程。现代计算机技术、条码技术、条码技术等都不能满足传统的固资管理机制&#xff0c;RFID技术、硬件扫描技术、提高固定资产管理流程、固定资产管理和统计等方面的特殊要求。科学规范地管控企业有形资产的…

日常环境配置

pip install 使用代理 例&#xff1a;代理端口&#xff1a;10808 pip install akshare --proxyhttp://127.0.0.1:10808———— conda 虚拟环境安装pip包 查看虚拟环境地址 conda info --env #查看虚拟环境地址使用–taget 安装pip 包 pip install akshare --target &q…

DataSphere Studio - 1.1.0安装部署 (单机版)

不要采用这种安装方式&#xff01;&#xff01;全部服务启动成功&#xff0c;不报错。页面还是各种报错&#xff0c;效率非常低下。 感谢微信群各位大佬帮助&#xff0c;分享了社区优秀文章中一键自动化部署脚本。 喝咖啡&#xff0c;自动部署&#xff0c;课本上故事成真。 自…

ChatGLM-6B模型使用

一、创建环境 conda create -n chatglm python3.8 conda activate chatglm 二、下载代码 git clone https://github.com/THUDM/ChatGLM-6B.git三、安装依赖 我这里是cuda11.2&#xff0c;根据自己的版本安装&#xff0c;这里是pytorch版本&#xff1a;https://pytorch.org/g…

linux系统编程重点复习--文件和目录操作

目录 复习目标 1.文件IO 2.C标准函数与系统函数的区别 2.1什么是系统调用 2.2 文件描述符 2.3 open函数 2.4 close函数 2.5 read/write 2.5.1read函数 2.5.2 write 2.6 lseek 2.7 perror和errno 2.8 阻塞和非阻塞: 3文件和目录 3.1 stat/lstat函数 3.2 opendir…

在使用Python爬虫时遇到解析错误解决办法汇总

在进行Python爬虫任务时&#xff0c;遇到解析错误是常见的问题之一。解析错误可能是由于网页结构变化、编码问题、XPath选择器错误等原因导致的。为了帮助您解决这个问题&#xff0c;本文将提供一些实用的解决办法&#xff0c;并给出相关的代码示例&#xff0c;希望对您的爬虫任…

【好书推荐】ChatGPT入门经典《这就是 ChatGPT》

文章目录 一、前言二、通俗易懂三、传奇大佬四、精彩的导读序五、总结 一、前言 目前很少能有一本书&#xff0c;能做到一定深度地普及 ChatGPT 的原理&#xff0c;而这本书可以做到恰到好处地告诉大家&#xff0c;ChatGPT 是如何工作的。 二、通俗易懂 ChatGPT 是一种人工智…