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

news2024/12/23 14:31:18

前言:

在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的动态加载能力和模块化开发至关重要。

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

 

 

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

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

相关文章

Shopify怎么避免被封店?封店原因有哪些?

市场研究的一份报告显示&#xff0c;全球跨境电子商务市场预计到2028年将达到30422亿美元&#xff0c;其中&#xff0c;亚太地区是最大的跨境电商市场&#xff0c;据海关统计数据&#xff0c;近五年来&#xff0c;我国跨境电商进出口增长近10倍。跨境电商业务新的增长风口已经到…

她经济新风尚:品牌如何通过5大策略引领海外女性市场

随着社会的不断进步和女性地位的提升&#xff0c;海外女性市场已经成为全球商业的一个巨大引擎。越来越多的品牌意识到&#xff0c;了解并满足女性消费者的需求是推动业务增长的关键。在这个充满机遇和挑战的“她经济”时代&#xff0c;品牌需要制定切实可行的策略&#xff0c;…

【FPGA/verilog -入门学习6】verilog频率计数器

需求 在使能信号控制下&#xff0c;计算输入脉冲的每两个上升沿之间的时钟周期数并输出&#xff0c;即输出脉冲频率的计数值 输入信号 周期性脉冲信号&#xff1a;需要做检测的脉冲频率信号 使能信号&#xff1a;高电平进行频率计数&#xff0c;低电平清零计数器 输出信号 计数…

微软自带浏览器Edge,无法关闭“保存历史记录网站的屏幕截图”解决方案

微软自带浏览器Edge&#xff0c;无法关闭“保存历史记录网站的屏幕截图”解决方案 吐槽1&#xff1a;Windows自带的Chrome内核版本的浏览器Microsofg Edge刚发布时可谓一股清流&#xff0c;启动速度快&#xff0c;占用内存较小&#xff0c;相信很多人也开始抛弃正代Chrome&…

如何分析自己的人格优势?

每一个人都有自己的人格&#xff0c;而每一种人格&#xff0c;也都有其优势。同样的事情&#xff0c;但我们每一个人的应对方式却完全不同&#xff0c;为什么会有这些差别&#xff1f;....... 如何扬长避短&#xff0c;找到自信&#xff0c;自我&#xff1f; 想要了解自己的人…

锂电3V升12V1A升压芯片WT3209

锂电3V升12V1A升压芯片WT3209 WT3209是一款高功率密度全集成BOOST升压转换器&#xff0c;具备高效能解决方案。3V升12V1A,5V升12V1A WT3209内部集成的功率MOSFET管导通电阻为上管13mΩ和下管11mΩ&#xff0c;具备2A开关电流能力&#xff0c;并且能够提供高达12.6V的输出电压。…

【IDEA】IntelliJ IDEA创建一个Maven项目

在IDEA中创建Maven项目&#xff0c;前提是已经安装配置好Maven环境 。 本文主要使用的是IntelliJ IDEA 2022.2.1 (Community Edition) 1.创建一个新project:File>Project 2.修改Maven配置&#xff1a;File>Settings>搜索maven 创建好的工程如下&#xff1a; src/main…

基于Spring Boot、Mybatis、Redis和Layui的企业电子招投标系统源码实现与立项流程

招投标管理系统是一款适用于招标代理、政府采购、企业采购和工程交易等领域的企业级应用平台。该平台以项目为主线&#xff0c;从项目立项到项目归档&#xff0c;实现了全流程的高效沟通和协作。通过该平台&#xff0c;用户可以实时共享项目数据信息&#xff0c;实现规范化管理…

滑动窗口经典问题

关卡名 滑动窗口高频问题 我会了✔️ 内容 1.掌握最长子串问题 ✔️ 2.理解长度最小的子数组问题 ✔️ 3.掌握盛水最多的容器问题 ✔️ 4.理解异位词问题如何解决 ✔️ 1 最长子串专题 先来看一道高频算法题&#xff1a;无重复字符的最长子串。具体要求是给定一个字符串…

vue 页面跳转时,浏览器上方显示进度条

vue 页面跳转时&#xff0c;浏览器上方显示进度条 文章目录 vue 页面跳转时&#xff0c;浏览器上方显示进度条先看效果一、安装 nprogress二、main.js 引入nprogress1.引入库 三、在router.js中对路由钩子进行设置四、测试 先看效果 vue 页面跳转时&#xff0c;浏览器上方显示进…

Windows Subsystem for Linux (WSL) 安装与使用笔记

文章目录 Part.I IntroductionPart.II 安装Chap.I 安装流程Chap.II 迁移至其他盘 Part.III 使用Chap.I 一些信息Chap.II 配置下载软件的源Chap.III 安装 pip Reference Part.I Introduction Windows Subsystem for Linux 简写为 WSL&#xff0c;是 Windows 的一个 Linux 子系统…

滑动窗口如人生,回顾往事不复还———力扣刷题

第一题&#xff1a;长度最小的子数组 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 思路&#xff1a; 第一想法肯定时暴力枚举&#xff0c;枚举数组任何一个元素&#xff0c;把他当起始位置&#xff0c;然后从起始位置找最短区间&#xff0c;使得…

经典综述|88.1分力作!土壤塑料际

柏林-勃兰登堡高级生物多样性研究所在《Nature reviews microbiology》期刊上(IF88.1)发表的“The soil plastisphere”研究论文中&#xff0c;综述了塑料对土壤的潜在影响。对土壤塑料际特性和微生物群落以及这些群落影响过程的阐明仍处于早期阶段&#xff0c;但进展的步伐很快…

linux中堡垒机

堡垒机 堡垒机概念目的 安装Jumpserver使用资产管理资产列表创建需要管理的服务器创建用户权限管理页面进行资产授权操作视频 应用管理应用管理页面创建需要管理的应用&#xff0c;这里用数据库mysql举例进入后点击创建资产管理创建登录应用所需的用户选择创建mysql关系型数据库…

C语言第四十六弹---最快方法找到杨氏矩阵中的数下标

C语言实现最快方法找到杨氏矩阵中数下标。 定义&#xff1a;杨氏矩阵是一种用于描述Young 表和表示论的工具&#xff0c;它在代数几何和组合数学中有广泛的应用。一个杨氏矩阵是一个以若干个正整数构成的矩形表格&#xff0c;且每行和每列的元素单调递增。 从定义中可获得条件…

初级数据结构(四)——队列

文中代码源文件已上传&#xff1a;数据结构源码 <-上一篇 初级数据结构&#xff08;三&#xff09;——栈 | NULL 下一篇-> 本篇是属于上一篇的补充篇&#xff0c;因为队列和栈的属性特别类似&#xff0c;很多细节部分可以查看上一篇或者初级据结构的第二…

Linux的基本指令和权限的知识

学前的建议&#xff1a;大家不要太关注指令是啥&#xff0c;记不住怎么办&#xff08;没事&#xff0c;想用时去查就好了&#xff09;&#xff0c;这篇文章重点部分是围绕指令的周边知识。毕竟指令是“死肌肉”&#xff0c;而一些关于Linux和操作系统的理论知识才是最重要滴&am…

图片水印怎么去掉?我来教你几招

图片水印怎么去掉&#xff1f;随着自媒体的不断孵化衍生&#xff0c;去水印也成为当下的热门话题之一&#xff0c;每天数以亿计的用户被图片水印所困扰&#xff0c;那么图片水印怎么去掉呢&#xff1f;今天我来教你几招&#xff0c;让你轻松搞定图片水印&#xff0c;一起来学习…

瑞典市场开发攻略,带你走进森林王国

瑞典人口超千万&#xff0c;是一个发达的北欧国家&#xff0c;是欧盟重要的成员国&#xff0c;与我国贸易往来密切&#xff0c;是我国外贸企业非常青睐的市场之一。诺贝尔奖想必大家都知道&#xff0c;诺贝尔就是瑞典人&#xff0c;除了诺贝尔和平奖之外&#xff0c;所有的奖项…

涉密网络的IP查询防护策略

涉密网络的安全性对于维护国家、企业及个人的核心利益至关重要。在当今数字化时代&#xff0c;网络攻击日益猖獗&#xff0c;其中IP查询是攻击者获取目标信息的一种常见手段。本文将探讨涉密网络中防护IP查询的关键策略&#xff0c;以确保网络的机密性和安全性。 1. 专用VPN和…