【从零开始学习JVM | 第四篇】类加载器的分类以及双亲委派机制

前言:

在Java编程中,类加载器(Class Loader)扮演着重要的角色。类加载器负责加载Java字节码并将其转换为可执行对象,使得我们能够在应用程序中使用各种类和资源。Java类加载器的设计和实现旨在支持动态扩展和模块化编程,为Java语言提供了很大的灵活性和可维护性。

类加载器的分类是理解Java类加载机制的关键部分。不同类型的类加载器负责加载不同类型的类,它们有着不同的加载策略和优先级。在本文中,我们将深入探讨类加载器的分类,并详细介绍每一种类加载器的特点和使用场景。

目录

前言:

类加载器: 

什么是类加载器: 

类加载器的应用场景:

类加载器的分类(JDK8以及之前版本): 

双亲委派机制:

什么是双亲委派机制:

双亲委派机制源码解读:

双亲委派机制的作用:

打破双亲委派机制:

总结:


 

类加载器: 

什么是类加载器: 

类加载器(ClassLoader)是JAVA虚拟机提供给应用程序去实现类和接口字节码数据的技术。

类加载器只参与加载过程中字节码获取并加载到内存中这一部分

类加载器的应用场景:

  1. 动态加载:类加载器允许在运行时动态地加载新的类和资源。这对于需要根据特定条件或用户需求加载不同类的应用程序非常有用。例如,插件系统和模块化开发中经常会使用类加载器动态加载和卸载模块,实现灵活的扩展和功能定制。

  2. 热部署:类加载器可以实现热部署,即在应用程序运行过程中替换和更新已加载的类。这使得我们可以在不停止应用程序的情况下对代码进行更新和修复。热部署在开发和调试阶段非常有用,能够提高开发效率和调试体验。

  3. 版本隔离:通过使用不同的类加载器加载不同版本的类,我们可以实现类的版本隔离。这在应用程序升级和依赖管理中十分重要,避免了不同版本的类之间的冲突和兼容性问题。

  4. 安全控制:类加载器可以应用安全策略,限制或控制特定代码和资源的访问权限。通过自定义类加载器,我们可以实现自定义的安全策略,对加载的类进行权限控制和验证。

  5. 字节码增强和动态代理:类加载器可以用于实现字节码增强和动态代理。通过自定义类加载器,我们可以在加载类的过程中修改字节码,添加额外的逻辑或功能。这对于AOP(面向切面编程)和代理模式等技术有着重要的应用。

而且类加载器的双亲委派机制也是面试中必问的一块内容,只要面试官问JVM相关的内容,那么双亲委派机制就一定是必问项。因此我们要学好类的加载器相关内容。

在了解了什么是类加载器以及其应用场景之后,我们来正式学习类加载器。


类加载器的分类(JDK8以及之前版本): 

  1. 启动类加载器(Bootstrap ClassLoader):它是JVM的一部分,负责加载Java核心类库,如java.lang包中的类。它是最顶层的类加载器,通常使用C++实现,无法在Java代码中直接获取到。(通用且重要)

  2. 扩展类加载器(Extension ClassLoader):它负责加载Java的扩展库,通常位于JRE的lib/ext目录下。它是由Java编写的,是由启动类加载器加载的。(通用但是不重要)

  3. 应用程序类加载器(Application ClassLoader):也称为系统类加载器(System ClassLoader),它负责加载用户类路径(Classpath)上指定的类库。它是开发者最常使用的类加载器,也是默认的类加载器。

除了上述三种常见的类加载器之外,还可以通过继承 java.lang.ClassLoader 类来自定义类加载器。自定义类加载器可以灵活加载类,实现各种特定需求,比如从网络下载类文件、解密等。

总结起来,常见的类加载器可以分为:启动类加载器、扩展类加载器、应用程序类加载器。开发者也可以根据需要自定义类加载器。

而在实际JAVA代码中,我们可能会遇到一个JAR包同时存在于多个类加载器加载范围的情况,此时我们就需要双亲委派机制来解决这个问题:

双亲委派机制:

什么是双亲委派机制:

双亲委派机制(Parent Delegation Model)是Java类加载器的一种工作方式,用于保证类的加载安全性一致性

根据双亲委派机制,当一个类加载器需要加载某个类时,它首先会委派给它的父类加载器去尝试加载。如果父类加载器能够成功加载该类,那么就返回该类的Class对象。如果父类加载器无法加载该类,子类加载器才会尝试自己去加载。

 

简单来讲:双亲委派机制的核心是解决一个类到底由谁进行加载的问题。而其过程就是向上查找是否加载过和向下尝试加载

注意点:不能认为一个加载器与其父类加载器的关系是继承,虽然有“父”。而本质是在加载器内部创建一个ClassLoader来存储其父类加载器。 

需要注意的是如果我们尝试获取扩展类加载器的parent对象,得到的结果是null。并不是说其父类不是启动类加载器,只是因为启动类加载器属于JVM的一部分,使用C++实现,无法被获取到! 

双亲委派机制源码解读:

整个双亲委派机制都是在Classload中进行的,因此我们主要看这部分源码:

 尝试加载一个类的时候,我们会调用loadClass方法,该方法的第一个参数为加载的类名第二个参数为是否对类进行解析

进入loadClass方法

 其实这段代码还是比较容易看懂的,整体的逻辑为:

  1. 使用findLoadedClass寻找目标类是否被加载
  2. 如果目标类没有被加载(c==null)那么就尝试寻找当前加载器的父类加载器,如果有父类加载器(parent!=null),就把当前类交给父类加载器执行loadClass方法。如果没有父类加载器,就让启动类加载器(BootstrapClassLoad)进行查找并加载
  3. 如果一直到顶层加载器,仍然无法加载目标类,那么我们就交由当前加载器进行加载(c=findClass(name)),并且记录一下时间等各种信息,然后return 0;
  4. 如果目标类已经被加载,直接return 0;

最后我们看一下 c=findClass(name)的findClass源码:

双亲委派机制的作用:

  1. 避免重复加载:通过使用双亲委派机制,每个类加载器在尝试加载某个类之前,都会先委托给它的父类加载器。这样可以避免同一个类被多个不同的类加载器加载,保证类的一致性,避免重复加载带来的冲突和内存浪费。

  2. 安全性保证:核心类库(如Java的核心类库)由启动类加载器负责加载,用户自定义的类则由应用程序类加载器加载。这样可以确保核心类库的安全性,防止用户自定义的类篡改核心类库的行为。

  3. 类的隔离性:不同的类加载器加载的类位于不同的命名空间中,彼此之间互相隔离。即使两个类的全限定名相同,但由不同的类加载器加载的类在JVM中也被视为不同的类。这种隔离性可以有效避免类的冲突,使得每个类加载器都可以独立加载和管理类。

  4. 扩展性:通过自定义类加载器,可以扩展Java的类加载机制,实现特定的加载需求。开发者可以自定义类加载器来实现类似热部署、动态加载等功能。自定义类加载器可以继承父类加载器的特性,并根据业务需求进行扩展。

总的来说,双亲委派机制可以保证类的一致性、安全性和隔离性,避免重复加载,同时也提供了灵活的扩展性,使得类加载器可以根据特定需求进行定制。

而虽然双亲委派机制为JAVA类的加载提供了很好的安全性便捷性。但是有的时候我们不得不打破双亲委派机制,例如:

一个Tomcat容器中可以运行多个WEB应用,而如果这两个应用中出现了同名的A类,那么Tomcat就要保证这两个A类都被加载并且是各自不同的类。如果不打破双亲委派机制,那么WEB1中的A类记载后,WEB2中自己的A类就不会加载成功了,按照双亲委派机制来讲,此时会直接返回WEB1中的A类。此时我们就需要打破双亲委派机制。

打破双亲委派机制:

1.自定义类加载器并且重写Classload(Tomcat使用策略):

通过上文我们对源码单独阅读,相信大家已经理解了双亲委派机制的基本流程。而我们如果想要打破双亲委派机制,重写一下Classload方法就好,具体地讲,是重写以下代码块:

代码示例:

public class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // 首先检查类是否已经被加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                // 检查类是否在系统类加载器中已经加载
                c = findClass(name);
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 在这里实现自定义的类加载逻辑
        // 可以从其他位置加载类的字节码,并使用 defineClass() 方法定义类
    }
}

但需要注意的是,在这段代码的逻辑中,虽然我们没有给自定义类加载任何父类加载器,但是他也会有一个默认的父类加载器 应用程序类加载器,只不过我们重写loadClass的时候并没有用到父类加载器而已。

如果我们只是想自定义一个加载器,自主加载一些类。此时就不应该打破双亲委派机制,而是选择在FindClass中进行重写

2.线程上下文类加载器(JDBC使用策略):

JDBC在尝试连接数据库的时候会使用到一个叫做DriveManager的包来管理各种数据库驱动和加载相关驱动:

String url = "jdbc:mysql://localhost:3306/your_database_name";
String username = "your_username";
String password = "your_password";
Connection connection = DriverManager.getConnection(url, username, password);

 DriveManager位于rt.jar中,由启动类加载器进行加载。

而这个包又要去加载各种数据库驱动类。而这种第三方的包又要在应用程序加载类中进行加载。那么就出现了一个问题

也就是说启动类加载器加载完DriveManager之后,对于其需要加载的各种数据库驱动,启动类加载器是无法进行加载的,他只能交给应用程序类加载器进行加载。这不就打破了双亲委派机制的从下向上委托原则

我们一步一步来解析,首先先来介绍一下SPI机制:

        SPI(Service Provider Interface)是Java提供的一种服务提供发现机制。它允许开发人员定义服务接口,并允许第三方厂商通过在应用程序的类路径下提供实现来扩展应用程序的功能。

SPI机制的工作原理如下:首先,开发人员定义一个服务接口,以及对该接口提供服务实现的一个或多个类。然后,在应用程序的类路径中创建一个配置文件,该文件的名称必须是"META-INF/services/接口全限定名",其中,以接口全限定名作为文件名,其内容则是服务接口实现类的全限定名。

当应用程序初始化时,Java运行时会利用Java的反射机制从类路径下的配置文件中读取并加载服务接口的实现类。这样,应用程序就能够获取到实现类的实例,并使用其提供的功能。

而我们的DriveManager就是通过SPI机制快速找到JaR包要加载的驱动的。

而基于SPI机制,DriveManager被加载的整体过程为:

在了解了基于基于SPI机制下DriveManager被加载的整体过程后,我们再来想一想:SPI机制是如何打破双亲委托机制下的从下委托的呢?

在SPI机制中,通常使用线程上下文类加载器(Thread Context Class Loader)来加载具体的实现类。线程上下文类加载器是在多线程环境中引入的概念,用于指定每个线程的类加载器。线程上下文类加载器通常通过Thread.currentThread().setContextClassLoader()方法进行设置。

在SPI机制中,通过线程上下文类加载器,可以解决在双亲委托模型下从底层向上委托的问题。具体来说,当SPI实现框架的代码位于一个类库中,而由应用程序自定义的SPI实现类位于应用程序的类路径下时,由于双亲委托模型的限制,无法直接由应用程序加载SPI实现类。此时可以通过在应用程序中使用线程上下文类加载器来加载SPI实现类,即将线程上下文类加载器设置为应用程序的类加载器。这样,SPI实现框架就可以通过线程上下文类加载器加载应用程序中的SPI实现类,从而打破了双亲委托模型的限制。

需要注意的是,SPI机制依赖于线程上下文类加载器的正确设置,因此在使用SPI机制时,需要确保正确设置线程上下文类加载器,以保证SPI实现框架能够正确加载应用程序中的SPI实现类。

简单来讲:SPI有上下文类加载器,他可以提前保存好一个应用类程序加载器。然后当我们使用启动类加载器加载DriveManager,而DriveManager需要加载数据库驱动的时候,DriveManager就会调用上下文类加载器,使得当前加载器从启动类加载变为应用类加载器

但其实对于上下文加载器打破双亲委派机制这种方式呢,普遍还是存在争议的。

  • 有人认为他确实打破双亲委派机制:因为 DriveManager 由启动类加载器加载,却在记载过程中需要委派程序类加载器进行记载,打破了双亲委派机制的委派是从上到下的规则。
  • 有人认为他没有打破双亲委派机制:因为在整个加载类的过程中,DriveManager在java核心包rt.jar中,因此被启动类加载器加载;jar包中的数据库驱动属于第三方包,因此被从应用程序类加载器加载。不管是DriveManager类还是数据库驱动类的加载,都没有重写loadClass方法,只要你使用的是原生的loadClass,你就仍然遵循双亲委派机制

3.OSGI框架的类加载器:

历史上OSGI模块化框架打破了双亲委派机制,它存在同级之间的类记载器的委托加载。

OSGi(开放服务网关)是一个用于构建模块化、动态、可扩展的Java应用程序的规范和框架。

模块化是指将应用程序拆分为多个独立的模块(也称为bundle),每个模块包含自己的代码和资源。这种模块化的设计使得开发人员可以更加灵活地管理和维护应用程序,提高了可重用性和可维护性。

最早的时候JAVA是没有模块化的思想的,所有的jar包都在rt.jar中进行管理,而OSGi就提供了一种方式将功能相近的jar包放入到一个jar包进行统一管理。

在OSGi框架中,每个模块被称为一个bundle(捆绑包),bundle可以包含自己的类和资源。OSGi使用了自己的类加载器实现,称为BundleClassLoader。

BundleClassLoader是OSGi框架中的核心类加载器,它在加载类时打破了双亲委派机制。它首先尝试自己加载类,如果找不到所需的类,则会委托给父类加载器。这种机制与标准的双亲委派机制不同,因为BundleClassLoader首先尝试自己加载,并不一定按照父优先的原则。

总结:

类加载器和双亲委派机制是Java中关键的概念。通过类加载器,Java程序能够动态加载类,实现代码的灵活性和可扩展性。双亲委派机制保证了类的唯一性和一致性,避免了重复加载和冲突。了解类加载器和双亲委派机制对于解决类路径冲突、实现模块化开发和保证安全性至关重要。它们对于掌握Java动态加载能力和模块化开发具有重要意义。深入理解其原理和机制,能更好地利用Java的灵活性和可扩展性,开发出优秀的应用程序。在某些情况下,可能需要定制和扩展类加载器行为,进一步发挥其作用。总之,类加载器和双亲委派机制是Java虚拟机的核心组成部分,对于理解和掌握Java的动态加载能力和模块化开发至关重要。

如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用vue UI安装路由插件

1.使用vue创建项目 vue create vue-appvue ui 2.使用vue ui界面创建管理项目 终端页面输入&#xff1a;vue ui 创建项目 安装完成。可以直接在ui界面运行&#xff0c;也可以在编辑器中使用命令运行 安装路由&#xff0c;安装状态 选择插件 - 添加vue-router、添加vuex 安装…

时间序列预测 — GRU实现多变量多步光伏预测(Tensorflow)

目录 1 数据处理 1.1 数据集简介 1.2 导入库文件 1.3 数据集处理 1.4 训练数据构造 2 模型训练与预测 2.1 模型训练 2.2 模型多步预测 2.3 预测可视化 1 数据处理 1.1 数据集简介 实验数据集采用数据集7&#xff1a;常州普利司通光伏数据集&#xff08;下载链接&…

Redis中HyperLogLog的使用

目录 前言 HyperLogLog 前言 在学习HyperLogLog之前&#xff0c;我们需要先学习两个概念 UV&#xff1a;全称Unique Visitor&#xff0c;也叫独立访客量&#xff0c;是指通过互联网访问、浏览这个网页的自然人。1天内同一个用户多次访问该网站&#xff0c;只记录1次。PV&am…

Ribbon组件的负载均衡原理

原因背景 spring cloud的底层负载均衡是采用Ribbon组件&#xff0c;我们将user-service服务注册到eureka-server中&#xff0c;那么当我们在另一个服务的代码层面请求远程调用API接口http://user-service/users/5时&#xff0c;程序代码如何解析远程调用的user-service服务名转…

PyTorch机器学习与深度学习实践技术应用

近年来&#xff0c;随着AlphaGo、无人驾驶汽车、医学影像智慧辅助诊疗、ImageNet竞赛等热点事件的发生&#xff0c;人工智能迎来了新一轮的发展浪潮。尤其是深度学习技术&#xff0c;在许多行业都取得了颠覆性的成果。另外&#xff0c;近年来&#xff0c;Pytorch深度学习框架受…

在re:Invent大会上,上汽海外出行选择亚马逊云科技为其提供智能网联解决方案

亚马逊云科技在re:Invent 2023上宣布&#xff0c;中国最大的汽车制造商之一、《财富》世界500强企业上汽集团旗下的上汽海外出行科技有限公司&#xff08;以下简称“上汽海外出行”&#xff09;已选择亚马逊云科技为重要云服务供应商&#xff0c;为出海的自主品牌汽车构建领先的…

NFC和蓝牙在物联网中有什么意义?如何选择?

#NFC物联网# #蓝牙物联网# 在物联网中&#xff0c;NFC和蓝牙有什么意义&#xff1f; NFC在物联网中代表近场通信技术。它是一种短距离、高频的无线通信技术&#xff0c;可以在近距离内实现设备间的数据传输和识别。NFC技术主要用于移动支付、电子票务、门禁、移动身份识别、防…

期末速成数据库极简版【查询】(3)

目录 多表查询 【8】多表连接——内连接 &#x1f642;等值连接 &#x1f642;自然连接 &#x1f642;非等值连接 【9】多表连接——外连接 【10】交叉连接不考 【11】联合查询 【12】扩展多表连接 【13】嵌套查询 &#x1f642; 多表查询 【8】多表连接——内连…

QT+Unity3D 超详细(将unity3D与QT进行连接,并实现信息传递)

QTUnity3D连接 在QT中连接unity3D&#xff0c;首先要有一个unity.exe执行文件。在这里不提供unity执行文件的编写&#xff0c;只提供QT这边与unity3D连接和信息传递。 创建项目 创建一个新的项目&#xff0c;我创建的项目名称如下。 下图是我建立新项目的文件。APP文件就是…

Python查看文件列表

os.listdir 是 Python 的一个内置函数&#xff0c;用于列出指定目录中的所有文件和子目录。它接受一个字符串参数&#xff0c;即要列出内容的目录的路径。 列出当前工作目录中的所有文件和子目录 files_and_dirs os.listdir() print(files_and_dirs) 列出指定目录中的所…

User: zhangflink is not allowed to impersonate zhangflink

使用hive2连接进行添加数据是报错&#xff1a; [08S01][1] Error while processing statement: FAILED: Execution Error, return code 1 from org.apache.hadoop.hive.ql.exec.mr.MapRedTask. User: zhangflink is not allowed to impersonate zhangflink 有些文章说需要修…

解决:spring boot+mybatis进行增删改查的时候,接收到前端数据,并且执行成功了,但是数据库里面添加的内容都是空值

在写spring boot整合mybatis的时候&#xff0c;我在Apifox里面测试&#xff0c;数据也传递过去了&#xff0c;后端服务器也接收到了参数&#xff0c;就是数据库里面添加的都是空值&#xff1f;&#xff1f;&#xff1f; 前端接收到了数据&#xff0c;并且没有报错 Apifox里面也…

Kubernetes入门笔记——(2)k8s设计文档

​k8s最初源自谷歌的Brog项目&#xff0c;架构与其类似&#xff0c;主要包括etcd、api server、controller manager、scheduler、kubelet和kube-proxy等组件 etcd&#xff1a;分布式存储&#xff0c;保存k8s集群的状态 api server&#xff1a;资源操作的唯一入口&#xff0c;…

使用GPT-4V解决Pycharm设置问题

pycharm如何实现关联&#xff0c;用中文回答 在PyCharm中关联PDF文件类型&#xff0c;您可以按照以下步骤操作&#xff1a; 1. 打开PyCharm设置&#xff1a;点击菜单栏中的“File”&#xff08;文件&#xff09;&#xff0c;然后选择“Settings”&#xff08;设置&#xff09;。…

STM32——震动传感器点亮LED灯

震动传感器简单介绍 若产品不震动&#xff0c;模块上的 DO 口输出高电平&#xff1b; 若产品震动&#xff0c;模块上的 DO 口输出低电平&#xff0c;D0-LED绿色指示灯亮。 震动传感器与STM32的接线 编程实现 需求&#xff1a;当震动传感器接收到震动信号时&#xff0c;使用中断…

同旺科技 USB TO RS-485 定制款适配器--- 拆解(四)

内附链接 1、USB TO RS-485 定制款适配器 ● 支持USB 2.0/3.0接口&#xff0c;并兼容USB 1.1接口&#xff1b; ● 支持USB总线供电&#xff1b; ● 支持Windows系统驱动&#xff0c;包含WIN10 / WIN11系统32 / 64位&#xff1b; ● 支持Windows RT、Linux、Mac OS X、Windo…

Windows Installer服务启动不了问题解决办法

本章教程主要提供在win10和win11 系统服务Windows Installer&#xff0c;处于灰色状态&#xff0c;无法启动的时候的一种解决方法。 目录 解决办法 1、进入注册表 2、找到以下路径 解决办法 1、进入注册表 regedit 2、找到以下路径 HKEY_LOCAL_MACHINE\SYSTEM\ControlSet00…

bert其他内容个人记录

Pre-training a seq2seq model BERT只是一个预训练Encoder&#xff0c;有没有办法预训练Seq2Seq模型的Decoder&#xff1f; 在一个transformer的模型中&#xff0c;将输入的序列损坏&#xff0c;然后Decoder输出句子被破坏前的结果&#xff0c;训练这个模型实际上是预训练一个…

OpenHarmony北向-让更广泛的应用开发者更容易参与

一、标准系统的体验 按照官方文档指导&#xff0c;这样操作&#xff0c;OH标准系统开发板就可以运行开发者开发的OpenHarmony应用了。 二、实际情况 按照开发文档上的说明&#xff0c;肯定是装不上的。因为OH不同的发行版&#xff0c;不同发行板不同的设备&#xff0c;IDE&…

使用Java8的Stream流的Collectors.toMap来生成Map结构

问题描述 在日常开发中总会有这样的代码&#xff0c;将一个List转为Map集合&#xff0c;使用其中的某个属性为key&#xff0c;某个属性为value。 常规实现 public class CollectorsToMapDemo {DataNoArgsConstructorAllArgsConstructorpublic static class Student {private…