类加载器(classloader)

news2024/11/26 18:32:28

作者:ZeaTalk
链接:https://www.zhihu.com/question/49667892/answer/690161827
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 

类加载器(classloader)

先从类加载器说起,凡事先问是什么,首先什么是类加载器?

我们知道,一个 *.java 的代码源文件要执行起来之前,必须通过 javac 构建抽象语法树并编译成字节码,字节码仍然是不能被机器所识别,那么一个 .class 文件要被机器识别并执行的前提就是将字节码转化成机器码加载到内存里,这一转化过程就是类加载的执行过程。


当然,这整个过程细节并非这个问题的讨论重点。

类加载器便是在在这个过程里的加载阶段起作用,负责将 .class 文件字节码提取出来,转化成二进制字节流。

(但是这离成为真正的类还有十万八千里。。。)

一个Java应用中通常存在三个类加载器(Classloader):

  • Bootstrap Classloader 启动类加载器:负责加载<JRE>/lib 下的核心类库,此加载器本身内嵌于JVM,在 Java 中找不到具体的引用;
  • Extension Classloader 扩展类加载器:负责加载<JRE>/lib/ext 下的扩展类库;
  • Application Classloader 系统应用类加载器:负责-classpath 指定路径类库加载。

他们三者并非典型的继承关系,而是子指派父为自己的 parent。

(这里有个源码细节,由于启动类加载器是内嵌于 JVM 且无法被引用,因此 Extension Classloader 指派 parent 为 null,即等同于指派启动类加载器为自己的父加载器)

为了有足够灵活性,类加载器也是允许自定义的。这不禁思考,这么多类加载器之间是怎么协调类加载任务的?

这就引出了本文的重点,双亲委派模型。

双亲委派(parents deletation model)

双亲委派模型是什么?

The Java platform uses a delegation model for loading classes. The basic idea is that every class loader has a "parent" class loader. When loading a class, a class loader first "delegates" the search for the class to its parent class loader before attempting to find the class itself.

由于翻译问题,这里双亲的 “双” 并没有太多特殊含义,双亲就是父母。简单来说,当一个类加载器得到一个类加载任务 t 的时候,首先会委派其 parent A 去加载,A 拿到任务后,也会进一步委派到 A 的 parent B。层层向上递归直到委派到启动类加载器。

但我们知道,每个 classloader 负责的加载域是不一样的,启动类加载器需根据 t 给出的类全限定名(如 com.Test)在其所负责的域里搜寻此类字节码,如果找到,则加载之;如果找不到,则表示无法加载,把代理权限往下(父->子)转移,直到某个加载器在负责的加载域中找到该类为止。

这一逻辑的代码实现是这样的:

java.lang.ClassLoader#loadClass(java.lang.String, boolean)

synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> 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 stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }

findLoadedClass :先确认类加载任务指定的类全限定名是否已经被加载,没有加载过才能委派;

findBootstrapClassOrNull :如果 parent 为空这是指定了 bootstrap 加载器。

为什么要有双亲委派模型?


一个类在同一个类加载器中具有唯一性(Uniqueness),而不同类加载器中是允许同名类存在的,这里的同名是指全限定名相同。但是在整个JVM里,纵然全限定名相同,若类加载器不同,则仍然不算作是同一个类,无法通过 instanceOf 、equals 等方式的校验。

由于唯一性的存在,Class 被替换就有可能了,而双亲委派模型定义了一套类加载的优先层级,很好的防止核心类库被恶意替换。毕竟核心类库是 bootstrap classloader 加载的,而 bootstrap 是内嵌于JVM的,在双亲委派模型基础上,任何类加载任务都会交由 bootstrap classloader 这位大佬经手过目一次,只要是核心类库中的类,都会被 bootstrap classloader 加载,间接确保核心类库不被其他类加载器加载。

换言之,在遵循了双亲委派模型的规则之下,是不允许出现核心类库被替换或取代的可能,即不能在自己的 classpath 定义 java.lang.*之类的Class去替换JRE 中的Class。

classloader 加载模型是否适用所有场景?

未必。这个模型最大的局限在于,假定 A 作为 B 的 parent,A 加载的类 对 B 是可见的; 然而 B 加载的类 对 A 却是不可见的。

这是由 classloader 加载模型中的可见性(visibility)决定的

Visibility principle allows child class loader to see all the classes loaded by parent ClassLoader, but parent class loader can not see classes loaded by child.

最典型不适用的场景便是 SPI 的使用。

SPI(Service Provider Interface)

Java 在核心类库中定义了许多接口,并且还给出了针对这些接口的调用逻辑,然而并未给出实现。开发者要做的就是定制一个实现类,在 META-INF/services 中注册实现类信息,以供核心类库使用。

java.sql.Driver 是最为典型的 SPI 接口,java.sql.DriverManager 通过扫包的方式拿到指定的实现类,完成 DriverManager的初始化。

等等,似乎有什么不对,根据双亲委派的可见性原则,启动类加载器 加载的 DriverManager 是不可能拿到 系统应用类加载器 加载的实现类 ,这似乎通过某种机制打破了双亲委派模型。

双亲委派模型并非强制模型

SPI 是如何打破双亲委派模型的呢?

java.sql.DriverManager#loadInitialDrivers

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
​
/* Load these drivers, so that they can be instantiated.
 * It may be the case that the driver class may not be there
 * i.e. there may be a packaged driver with the service class
 * as implementation of java.sql.Driver but the actual class
 * may be missing. In that case a java.util.ServiceConfigurationError
 * will be thrown at runtime by the VM trying to locate
 * and load the service.
 *
 * Adding a try catch block to catch those runtime errors
 * if driver not available in classpath but it's
 * packaged as service and that service is there in classpath.
 */
try{
    while(driversIterator.hasNext()) {
        driversIterator.next();
    }
} catch(Throwable t) {
// Do nothing
}
return null;

java.util.ServiceLoader#load(java.lang.Class<S>)

public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

通过从线程上下文(ThreadContext)获取 classloader ,借助这个classloader 可以拿到实现类的 Class。

(源码上讲,这里是通过 Class.forName 配合 classloader拿到的)

线程上下文 classloader并非具体的某个loader,一般情况下是 application classloader, 但也可以通过 java.lang.Thread#setContextClassLoader 这个方法指定 classloader。

综上,为什么说 Java SPI 的设计会违反双亲委派原则呢?

首先双亲委派原则本身并非 JVM 强制模型。

SPI 的调用方和接口定义方很可能都在 Java 的核心类库之中,而实现类交由开发者实现,然而实现类并不会被启动类加载器所加载,基于双亲委派的可见性原则,SPI 调用方无法拿到实现类。

SPI Serviceloader 通过线程上下文获取能够加载实现类的classloader,一般情况下是 application classloader,绕过了这层限制,逻辑上打破了双亲委派原则。

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

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

相关文章

【数据结构】直接插入排序

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;数据结构 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵 希望大佬指点一二 如果文章对你有帮助…

第八章:枚举

系列文章目录 文章目录 系列文章目录前言一、枚举总结 前言 类可以作为常量使用。 一、枚举 枚举是一组常量的集合。可以这里理解&#xff1a; 枚举属于一种特殊的类&#xff0c; 里面只包含一组有限的特定的对象。 枚举的实现方式自定义类实现枚举使用 enum 关键字实现枚举…

MySQL 分库分表与 TiDB(平凯数据库),如何选择?

随着互联网行业的飞速发展&#xff0c;数据量不断增长&#xff0c;传统的关系型数据库已经无法满足大规模数据处理的需求。为了解决这一问题&#xff0c;分库分表和分布式数据库应运而生。本文将对比分析 MySQL 分库分表和 TiDB 这两种解决方案&#xff0c;帮助大家更好地选择适…

自动化测试和手工测试有什么不同以及自动化测试和手工测试应用范围的对比

一、初识自动化测试 如果以前没有做过自动化测试&#xff0c;那么就不了解自动化测试&#xff0c;可能会觉得自动化测试比较神秘&#xff0c;但是&#xff0c;我们在日常的计算机操作中&#xff0c;可能会碰到一些自动化处理的过程&#xff0c;这些过程和自动化测试比较接近。 …

Mysql修改事务隔离级别及与spring隔离级别关系

Mysql如何修改事务隔离级别 1.查询事务级别 1.1查询全局事务隔离级别 select global.tx_isolation; 1.2 查询当前会话事务隔离级别 select session.tx_isolation; 2.修改事务隔离级别 2.1 修改全局事务隔离级别 set global transaction isolation level read committed;…

Kafka 的应用场景

Kafka 是一个开源的分布式流式平台&#xff0c;它可以处理大量的实时数据&#xff0c;并提供高吞吐量&#xff0c;低延迟&#xff0c;高可靠性和高可扩展性。 Kafka 最初是为分布式系统中海量日志处理而设计的。它可以通过持久化功能将消息保存到磁盘&#xff0c;并让消费者按…

景联文科技入选量子位智库《中国AIGC数据标注产业全景报告》数据标注行业代表机构TOP20

量子位智库《中国AIGC数据标注产业全景报告》中指出&#xff0c;数据标注处于重新洗牌时期&#xff0c;更高质量、专业化的数据标注成为刚需。未来五年&#xff0c;国内AI基础数据服务将达到百亿规模&#xff0c;年复合增长率在27%左右。 基于数据基础设施建设、大模型/AI技术理…

areca backup备份工具安装与使用

由于FTP数据备份执行&#xff0c;需要人工操作执行&#xff0c;不满足业务需求&#xff0c;发现此工具结合ftp联动可以定时任务进行备份 获取地址 https://nchc.dl.sourceforge.net/project/areca/areca-stable/areca-7.5/areca-7.5-windows-jre64-setup.exe 前提条件 注意…

关闭RecyclerView惯性滚动,以及多个RecyclerView在嵌套滚动中的注意事项

前言&#xff1a; 当前RecyclerView 下拉到顶部 或者 上拉到底部时&#xff0c;虽然滚动列表停止了&#xff0c;但惯性任务并没有结束&#xff0c;一些特殊需求可能受到影响&#xff0c;需要手动停止。 1. RecyclerView源码 调用 rv.stopScroll() 停止&#xff1b; 2. Recycl…

数据库sql语句设置外键

当我们需要在数据库表之间建立关联关系时&#xff0c;可以使用外键&#xff08;Foreign Key&#xff09;来实现。在 SQL 中&#xff0c;外键可以用来保持数据的完整性&#xff0c;并帮助我们更有效地管理数据。以下是设置外键的步骤&#xff1a; 1.在创建表时&#xff0c;需要…

公益SRC实战|SQL注入漏洞攻略

目录 一、信息收集 二、实战演示 三、使用sqlmap进行验证 四、总结 一、信息收集 1.查找带有ID传参的网站&#xff08;可以查找sql注入漏洞&#xff09; inurl:asp idxx 2.查找网站后台&#xff08;多数有登陆框&#xff0c;可以查找弱口令&#xff0c;暴力破解等漏洞&…

7个免费的优质图标素材网站,设计师必备!

对于交互设计师和产品经理来说&#xff0c;一套漂亮易用的图标可以算是提高效率的法宝&#xff0c;自己导出一套标准化的图标是一个巨大的工程。如何找到一个既美观又实用又能快速重用的图标&#xff1f;别慌&#xff0c;今天我们整理了7个价值高又好用的图标素材网站&#xff…

wx.canvasToTempFilePath生成图片保存到相册

微信小程序保存当前画布指定区域的内容导出生成指定大小的图片&#xff0c;记录一下 api&#xff1a;wx.canvasToTempFilePath 效果&#xff1a; 代码&#xff1a;wxml <canvas style"width: {{screenWidth}}px; height: {{canvasHeight}}px;" canvas-id"my…

芯科科技推出新的8位MCU系列产品,扩展其强大的MCU平台

新的BB5系列为简单应用提供更多开发选择 中国&#xff0c;北京 - 2023年11月14日 – 致力于以安全、智能无线连接技术&#xff0c;建立更互联世界的全球领导厂商Silicon Labs&#xff08;亦称“芯科科技”&#xff0c;NASDAQ&#xff1a;SLAB&#xff09;&#xff0c;今日宣布…

浅析SVPWM调制技术

目录 收起 目录 目录 一、SVPWM基本原理 1.从三相电机运行原理到SVPWM调制技术 2.SVPWM调制的实现方法 3.合成参考电压矢量的方法 &#xff08;abc->αβ坐标变换&#xff09; 4.基本电压矢量时间的分配 5.基本电压矢量顺序的分配 6.扇区判断 二、仿真验证 1.操…

什么是美颜SDK?美颜SDK对比评测

美颜SDK在视频直播中发挥着越来越重要的作用。为了实现实时、高质量的美颜效果&#xff0c;各种视频直播美颜SDK应运而生。本文将对这些技术进行深入解析与比较。 一、技术原理解析 深度学习技术通过大量的训练数据学习人脸特征&#xff0c;从而实现更为自然的美颜效果。传统…

PowerPoint技巧:如何将一张图片同时加到全部幻灯片里?

想把一张图片加到PPT每一张幻灯片的同一个位置&#xff0c;如果一张一张的添加就太耗时间了&#xff0c;一起来看看如何利用母版快速设置同时添加吧。 首先&#xff0c;打开需要编辑的PPT&#xff0c;在菜单栏依次点击【视图】→【幻灯片母版】&#xff1b; 打开母版后&#x…

2023年【安全员-C证】考试题库及安全员-C证考试总结

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 安全员-C证考试题库根据新安全员-C证考试大纲要求&#xff0c;安全生产模拟考试一点通将安全员-C证模拟考试试题进行汇编&#xff0c;组成一套安全员-C证全真模拟考试试题&#xff0c;学员可通过安全员-C证考试总结全…

【App测试】adb三大连接方式-夜神模拟器+真机+android真机(详细步骤)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 adb连接安卓模拟器…

AD教程 (十五)利用IPC封装创建向导快速创建封装

AD教程 &#xff08;十五&#xff09;利用IPC封装创建向导快速创建封装 安装IPC封装向导 点击头像&#xff0c;选择Extensions and Updates&#xff0c;扩展更新确保已经安装了IPC Footprint Generator IPC封装创建向导 打开IPC封装创建向导 进入PCB封装界面&#xff0c;点击…