类加载器(classloader)

作者: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,绕过了这层限制,逻辑上打破了双亲委派原则。

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

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

相关文章

如何使用AO交易和保护头寸,澳福3个指标轻松搞定

进入市场的第二个重要信号是基于投资者已经熟悉的令人敬畏的振荡器&#xff0c;称为AO。其工作原理类似于著名的MACD指示器&#xff0c;衡量市场的即时势头。它是一个5日均线减去34日均线。该指示器以红色和绿色柱状图的形式呈现&#xff0c;绿色柱状图表示市场势头向上&#x…

【Docker安装】windows系统安装docker

原文作者&#xff1a;我辈李想 版权声明&#xff1a;文章原创&#xff0c;转载时请务必加上原文超链接、作者信息和本声明。 文章目录 前言一、下载exe安装文件二、选择虚拟方式1.wsl2方式2.Hyper-V 三、启动docker Engine 守护进程1.启动docker 服务2.初始化守护进程 四、设置…

SpringBoot和Spring源码下载

1.下载&#xff1a;在一个空的干净地创建一个文件夹叫springsourcecode&#xff0c;其实叫什么都行的。 git clone https://github.com/spring-projects/spring-framework.git 2.JDK要和gradle匹配 我们要21的&#xff0c;今天为止2023年11月13日&#xff0c;idea是2023.2。 …

海报设计必备!五个免费网站分享,让你的创意得以充分展现!

海报作为一种重要的宣传工具&#xff0c;在各种场合得到了广泛的应用。然而&#xff0c;对许多人来说&#xff0c;制作一张漂亮的海报并不容易。幸运的是&#xff0c;有许多免费的海报制作网站可以帮助人们轻松地制作出漂亮的海报。本文将分享五个优秀的免费海报制作网站。 1.…

Power Apps-使用power Automate流

创建&#xff1a;Power Automate-创建power Apps使用的流-CSDN博客 打开Power Apps&#xff0c;创建页面&#xff0c;添加三个输入框&#xff08;分别是换算前单位、换算后单位、货币数&#xff09;&#xff0c;和一个文本框&#xff08;输出结果&#xff09;以及一个按钮 在…

天软特色因子看板 (2023.11 第10期)

该因子看板跟踪天软特色因子A05006(近一月单笔流入流出金额之比(%)该因子为近一个月单笔流入流出金额之比(%)均值因子&#xff0c;用以刻画 市场日内分时成交中流入、流出成交金额的差异性特点&#xff0c;发掘市场主力资金的作用机制。 今日为该因子跟踪第10期&#xff0c;跟踪…

qtcreator中其他控件的使用

scroll area 是 Tool Box是 tabwidget是网页 这三个可以用进行连接 用按钮实现切换 combo box的使用 使用label添加图片动图

Postman接口Mock Servier服务器

近期在复习Postman的基础知识&#xff0c;在小破站上跟着百里老师系统复习了一遍&#xff0c;也做了一些笔记&#xff0c;希望可以给大家一点点启发。 应用场景&#xff1a;后端的接口还没有开发完成&#xff0c;前端的业务需要调用后端的接口&#xff0c;可以使用mock模拟。 一…

csrf学习笔记总结

跨站请求伪造csrf csrf概述 掌握CSRF 漏洞原理 掌握CSRF 漏洞场景 掌握CSRF 漏洞验证 csrf原理 ​ 跨站请求伪造&#xff08;Cross Site Request Forgery&#xff0c;CSRF&#xff09;是一种攻击&#xff0c;它强制浏览器客户端用户在当前对其进行身份验证后的Web 应用程…

基于安卓android微信小程序的食谱大全系统

项目介绍 本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;它主要是采用java语言技术和mysql数据库来完成对系统的设计。整个开发过程首先对食谱大全进行需求分析&#xff0c;得出食谱大全主要功能。接着对食谱大全进行总体设计和详细设计。总体设…

利用网络管理解决方案简化网络运维

当今的网络正朝着提高敏捷性和动态功能的方向发展&#xff0c;以支持高级网络要求和关键业务流程&#xff0c;这导致 IT 基础架构也跨越无线、虚拟和混合环境。但是&#xff0c;随着网络的快速发展&#xff0c;如果没有合适的解决方案&#xff0c;IT 管理员很难管理它们&#x…

【功能栏】基于session的模拟短信注册功能

框架&#xff1a; spring boot mybatis-plus 目录 1.创建user表 ​编辑2. mybatis-plus插件 3.导入相关依赖 4.配置文件 5.前端代码 register.html style.css 6.后端代码 entity层 mapper层 sevice层 业务层接口 业务层实现类 controller层 7.调试 1. 未输…

【案例】由coredump引起的思考:程序的加载流程

背景 小米项目的同事&#xff0c;最近遇到了一个crash问题&#xff0c;困扰较久&#xff0c;向我请求帮助。&#xff08;其实是客户指定要求我来分析这个问题&#xff0c;该项目之前是由我负责的&#xff0c;帮客户解决很多较多crash问题。估计是实力得到了客户认可吧&#xff…

使用SSH和SCP传输文件———详细入门教学实践

确保你已经在本地机器上安装了SSH客户端和SCP工具。 获取远程虚拟机的IP地址或主机名以及登录凭据&#xff08;用户名和密码或私钥&#xff09;。 打开终端&#xff08;命令提示符&#xff09;并输入以下命令来传输文件&#xff1a; scp /本地路径/文件 用户名远程虚拟机IP地…

运动蓝牙耳机哪个品牌好?这五款骨传导耳机表现还不错!

作为一个经常跑步运动的人&#xff0c;总感觉运动能够让人暂时远离城市的喧嚣&#xff0c;同时运动也是一种特别好的舒压方法。但跑步的时候如果没有音乐助燃&#xff0c;那是没有灵魂的&#xff0c;这也许就是现代年轻人的矫情吧&#xff0c;我在运动的时候经常会佩戴骨传导耳…

nvm安装node,查看npm版本报错

nvm安装node&#xff0c;使用npm -v报如上图错误&#xff0c;是因为Node版本过高导致&#xff0c;可以降低node版本&#xff0c;使用低一级版本的node. nvm use 16.13.0后来我单独安装node 20.9.0就成功了&#xff0c;不再使用nvm安装Node

mfc140u.dll丢失怎么修复?4种亲测有效的方法分享

在计算机使用过程中&#xff0c;我们可能会遇到各种各样的问题&#xff0c;其中之一就是某些重要的dll文件丢失。DLL文件是动态链接库文件&#xff0c;它们包含了许多程序运行所需的函数和资源。当这些文件丢失或损坏时&#xff0c;可能会导致程序无法正常运行。本文将详细介绍…

完全未接触过软件测试的人,培训两个月就可上岗,这现实吗?

如果你想两个月能学完是可以的&#xff0c;但是只能做一些简单的功能测试&#xff0c;但也只限下面这四种情况 1.自身基础较好&#xff0c;自控力较强 比如一个计算机专业的学生要入行软件测试&#xff0c;可能就不需要进行入门培训了&#xff0c;自己找点视频看看就能很快上…

没有 SegWit 和 Taproot 的比特币序数

序号 &#xff08;Ordinals&#xff09;已成为 BTC 圈子中创建不可替代令牌&#xff08;NFT&#xff09;的热门话题。 它的主要特点是将 NFT 本身完全存储在链上。 我们表明原始的比特币协议已经可以支持它。 Ordinals 不需要任何重大更改&#xff0c;包括 SegWit 和 Taproot。…

骨传导蓝牙耳机哪款好?这五款骨传导耳机闭眼入都不会错!

随着科技的发展&#xff0c;数码产品更新换代的速度也是越来越快&#xff0c;如今无线蓝牙耳机已经占据主流&#xff0c;特别是运动爱好者&#xff0c;很多人都会为自己挑选一款好用的运动耳机&#xff0c;而骨传导耳机异军突起&#xff0c;凭借听歌不入耳、佩戴舒适稳固等特性…