【JVM】浅谈双亲委派和破坏双亲委派

转载自   【JVM】浅谈双亲委派和破坏双亲委派

一、前言

笔者曾经阅读过周志明的《深入理解Java虚拟机》这本书,阅读完后自以为对jvm有了一定的了解,然而当真正碰到问题的时候,才发现自己读的有多粗糙,也体会到只有实践才能加深理解,正应对了那句话——“Talk is cheap, show me the code”。前段时间,笔者同事提出了一个关于类加载器破坏双亲委派的问题,以我们常见到的数据库驱动Driver为例,为什么要实现破坏双亲委派,下面一起来重温一下。

 

二、双亲委派

想要知道为什么要破坏双亲委派,就要先从什么是双亲委派说起,在此之前,我们先要了解一些概念:

  • 对于任意一个类,都需要由加载它的类加载器和这个类本身来一同确立其在Java虚拟机中的唯一性

什么意思呢?我们知道,判断一个类是否相同,通常用equals()方法,isInstance()方法和isAssignableFrom()方法。来判断,对于同一个类,如果没有采用相同的类加载器来加载,在调用的时候,会产生意想不到的结果:

public class DifferentClassLoaderTest {public static void main(String[] args) throws Exception {ClassLoader classLoader = new ClassLoader() {@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";InputStream stream = getClass().getResourceAsStream(fileName);if (stream == null) {return super.loadClass(name);}try {byte[] b = new byte[stream.available()];// 将流写入字节数组b中stream.read(b);return defineClass(name, b, 0, b.length);} catch (IOException e) {e.printStackTrace();}return super.loadClass(name);}};Object obj = classLoader.loadClass("jvm.DifferentClassLoaderTest").newInstance();System.out.println(obj.getClass());System.out.println(obj instanceof DifferentClassLoaderTest);}
}

输出结果:

class jvm.DifferentClassLoaderTest
false

如果在通过classLoader实例化的使用,直接转化成DifferentClassLoaderTest对象:

DifferentClassLoaderTest obj = (DifferentClassLoaderTest) classLoader.loadClass("jvm.DifferentClassLoaderTest").newInstance();

就会直接报java.lang.ClassCastException:,因为两者不属于同一类加载器加载,所以不能转化!

 

2.1、为什么需要双亲委派

基于上述的问题:如果不是同一个类加载器加载,即时是相同的class文件,也会出现判断不想同的情况,从而引发一些意想不到的情况,为了保证相同的class文件,在使用的时候,是相同的对象,jvm设计的时候,采用了双亲委派的方式来加载类。

双亲委派:如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。

这里有几个流程要注意一下:

  1. 子类先委托父类加载
  2. 父类加载器有自己的加载范围,范围内没有找到,则不加载,并返回给子类
  3. 子类在收到父类无法加载的时候,才会自己去加载

jvm提供了三种系统加载器:

  1. 启动类加载器(Bootstrap ClassLoader):C++实现,在java里无法获取,负责加载/lib下的类。
  2. 扩展类加载器(Extension ClassLoader): Java实现,可以在java里获取,负责加载/lib/ext下的类。
  3. 系统类加载器/应用程序类加载器(Application ClassLoader):是与我们接触对多的类加载器,我们写的代码默认就是由它来加载,ClassLoader.getSystemClassLoader返回的就是它。

附上三者的关系:

 

2.2、双亲委派的实现

双亲委派的实现其实并不复杂,其实就是一个递归,我们一起来看一下ClassLoader里的代码:

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{// 同步上锁synchronized (getClassLoadingLock(name)) {// 先查看这个类是不是已经加载过Class<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {// 递归,双亲委派的实现,先获取父类加载器,不为空则交给父类加载器if (parent != null) {c = parent.loadClass(name, false);// 前面提到,bootstrap classloader的类加载器为null,通过find方法来获得} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {}if (c == null) {// 如果还是没有获得该类,调用findClass找到类long t1 = System.nanoTime();c = findClass(name);// jvm统计sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}// 连接类if (resolve) {resolveClass(c);}return c;}}

 

三、破坏双亲委派

3.1、为什么需要破坏双亲委派?

因为在某些情况下父类加载器需要委托子类加载器去加载class文件。受到加载范围的限制,父类加载器无法加载到需要的文件,以Driver接口为例,由于Driver接口定义在jdk当中的,而其实现由各个数据库的服务商来提供,比如mysql的就写了MySQL Connector,那么问题就来了,DriverManager(也由jdk提供)要加载各个实现了Driver接口的实现类,然后进行管理,但是DriverManager由启动类加载器加载,只能记载JAVA_HOME的lib下文件,而其实现是由服务商提供的,由系统类加载器加载,这个时候就需要启动类加载器来委托子类来加载Driver实现,从而破坏了双亲委派,这里仅仅是举了破坏双亲委派的其中一个情况。

3.2、破坏双亲委派的实现

我们结合Driver来看一下在spi(Service Provider Inteface)中如何实现破坏双亲委派。

先从DriverManager开始看,平时我们通过DriverManager来获取数据库的Connection:

String url = "jdbc:mysql://localhost:3306/testdb";
Connection conn = java.sql.DriverManager.getConnection(url, "root", "root"); 

在调用DriverManager的时候,会先初始化类,调用其中的静态块:

static {loadInitialDrivers();println("JDBC DriverManager initialized");
}private static void loadInitialDrivers() {...// 加载Driver的实现类AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);Iterator<Driver> driversIterator = loadedDrivers.iterator();try{while(driversIterator.hasNext()) {driversIterator.next();}} catch(Throwable t) {}return null;}});...
}

为了节约空间,笔者省略了一部分的代码,重点来看一下ServiceLoader.load(Driver.class)

public static <S> ServiceLoader<S> load(Class<S> service) {// 获取当前线程中的上下文类加载器ClassLoader cl = Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl);
}

可以看到,load方法调用获取了当前线程中的上下文类加载器,那么上下文类加载器放的是什么加载器呢?

public Launcher() {...try {this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);} catch (IOException var9) {throw new InternalError("Could not create application class loader", var9);}Thread.currentThread().setContextClassLoader(this.loader);...
}

sun.misc.Launcher中,我们找到了答案,在Launcher初始化的时候,会获取AppClassLoader,然后将其设置为上下文类加载器,而这个AppClassLoader,就是之前上文提到的系统类加载器Application ClassLoader,所以上下文类加载器默认情况下就是系统加载器

继续来看下ServiceLoader.load(service, cl)

public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader){return new ServiceLoader<>(service, loader);
}private ServiceLoader(Class<S> svc, ClassLoader cl) {service = Objects.requireNonNull(svc, "Service interface cannot be null");// ClassLoader.getSystemClassLoader()返回的也是系统类加载器loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;reload();
}public void reload() {providers.clear();lookupIterator = new LazyIterator(service, loader);
}

上面这段就不解释了,比较简单,然后就是看LazyIterator迭代器:

private class LazyIterator implements Iterator<S>{// ServiceLoader的iterator()方法最后调用的是这个迭代器里的nextpublic S next() {if (acc == null) {return nextService();} else {PrivilegedAction<S> action = new PrivilegedAction<S>() {public S run() { return nextService(); }};return AccessController.doPrivileged(action, acc);}}private S nextService() {if (!hasNextService())throw new NoSuchElementException();String cn = nextName;nextName = null;Class<?> c = null;// 根据名字来加载类try {c = Class.forName(cn, false, loader);} catch (ClassNotFoundException x) {fail(service,"Provider " + cn + " not found");}if (!service.isAssignableFrom(c)) {fail(service,"Provider " + cn  + " not a subtype");}try {S p = service.cast(c.newInstance());providers.put(cn, p);return p;} catch (Throwable x) {fail(service,"Provider " + cn + " could not be instantiated",x);}throw new Error();          // This cannot happen}public boolean hasNext() {if (acc == null) {return hasNextService();} else {PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {public Boolean run() { return hasNextService(); }};return AccessController.doPrivileged(action, acc);}}private boolean hasNextService() {if (nextName != null) {return true;}if (configs == null) {try {// 在classpath下查找META-INF/services/java.sql.Driver名字的文件夹// private static final String PREFIX = "META-INF/services/";String fullName = PREFIX + service.getName();if (loader == null)configs = ClassLoader.getSystemResources(fullName);elseconfigs = loader.getResources(fullName);} catch (IOException x) {fail(service, "Error locating configuration files", x);}}while ((pending == null) || !pending.hasNext()) {if (!configs.hasMoreElements()) {return false;}pending = parse(service, configs.nextElement());}nextName = pending.next();return true;}}

好了,这里基本就差不多完成整个流程了,一起走一遍:

 

四、总结

Driver剩余的加载过程就省略了,有兴趣的园友可以继续深入了解一下,不得不说,jvm博大精深,看起来容易,真正到了用起来才发现各种问题,也只有实践才能加深理解,最后谢谢各位园友观看,如果有描述不对的地方欢迎指正,与大家共同进步!

 

参考部分:

  • https://blog.csdn.net/yangcheng33/article/details/52631940
  • 周志明-《深入理解JAVA虚拟机:JVM高级特性与最佳实践》

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

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

相关文章

开源OSS.Social微信项目解析

前言&#xff1a;OSS.Social是个开源的社交网站接口集成项目&#xff0c;当前也有很多其他不错的项目&#xff0c;不过始终没有我想要的那种简单清晰&#xff0c;只能撸起袖子&#xff0c;从头打造一个。当前正在进行的是对微信项目的开发&#xff0c;这里把对接口的整理&#…

Apdex(Application Performance Index)量化应用性能

“道琼斯指数帮助人们衡量股市行情变化&#xff0c;Apdex 指数帮助您衡量用户心情变化。“ 一.为什么需要 Apdex 性能指数&#xff0c;Apdex(Application Performance Index)是一个国际通用标准&#xff0c;Apdex 是用户对应用性能满意度的量化值。它提供了一个统一的测量和报告…

云计算设计模式(一)缓存预留模式

云带来的改变是显而易见的&#xff0c;云计算是一种按使用量付费的模式&#xff0c;这种模式提供可用的、便捷的、按需的网络访问&#xff0c; 进入可配置的计算资源共享池&#xff08;资源包括网络&#xff0c;服务器&#xff0c;存储&#xff0c;应用软件&#xff0c;服务&am…

稀疏数组与二维数组相互转化

图示 二维数组转稀疏数组的思路 遍历 原始的二维数组&#xff0c;得到有效数据的个数 sum根据sum 就可以创建 稀疏数组 sparseArr int[sum 1] [3]将二维数组的有效数据数据存入到 稀疏数组 稀疏数组转原始的二维数组的思路 先读取稀疏数组的第一行&#xff0c;根据第一行的…

云计算设计模式(二)——断路器模式

云带来的改变是显而易见的&#xff0c;云计算是一种按使用量付费的模式&#xff0c;这种模式提供可用的、便捷的、按需的网络访问&#xff0c; 进入可配置的计算资源共享池&#xff08;资源包括网络&#xff0c;服务器&#xff0c;存储&#xff0c;应用软件&#xff0c;服务&am…

excel打开csv 出现乱码怎么解决 逗号分隔

excel打开csv 出现乱码怎么解决 https://jingyan.baidu.com/article/ac6a9a5e4c681b2b653eacf1.html CSV是逗号分隔值的英文缩写&#xff0c;通常都是纯文本文件。CSV格式是分隔的数据格式&#xff0c;有字段/列分隔的逗号字符和记录/行分隔换行符。通常CSV文件可以用EXCEL正常…

在ASP.NET Core下使用SignalR技术

一、前言 上次我们讲到过如何在ASP.NET Core中使用WebSocket,没有阅读过的朋友请参考 WebSocket in ASP.NET Core 文章 。这次的主角是SignalR它为我们提供了简化操作WebSocket的框架。 ASP .NET SignalR 是一个ASP.NET 下的类库&#xff0c;可以在ASP.NET 的Web项目中实现实时…

mybatis简单案例源码详细【注释全面】——前期准备

mybatis 是个什么东西&#xff0c;这里就不必说了&#xff0c;大家去网上搜搜看就行了&#xff0c;在这里我主要是分享一下最基本的增删改查案例以及配置信息&#xff0c;测试信息。 首先我们创建个数据库&#xff1a; /* SQLyog 企业版 - MySQL GUI v8.14 MySQL - 5.5.40 : …

云计算设计模式(三)——补偿交易模式

撤消由一系列步骤&#xff0c;它们共同限定了最终一致性操作中&#xff0c;如果一个或多个步骤失败执行的工作。按照最终一致性模型&#xff0c;业务实现复杂的业务流程和工作流的云托管的应用程序中很常见。 背景和问题 在云中运行的应用程序频繁修改数据。此数据可跨在各种地…

API网关Ocelot 使用Polly 处理部分失败问题

在实现API Gateway过程中&#xff0c;另外一个需要考虑的问题就是部分失败。这个问题发生在分布式系统中当一个服务调用另外一个服务超时或者不可用的情况。API Gateway不应该被阻断并处于无限期等待下游服务的状态。但是&#xff0c;如何处理这种失败依赖于特定的场景和具体服…

数组模拟环形队列

思路分析 代码实现 package com.atguigu.queue;import java.util.Calendar; import java.util.Scanner;/*** 创建人 wdl* 创建时间 2021/3/17* 描述*/ public class CircleArrayQueueDemo {public static void main(String[] args) {//测试一把System.out.println("测试数…

Error:(1, 10) java: 需要class, interface或enum

https://blog.csdn.net/zf18234031156/article/details/103047649 反复做这一步&#xff0c;30多个文件一个一个弄&#xff0c;有乱码用txt打开&#xff0c;黏贴覆盖&#xff0c; 目录 1.前言 2.原因(UTF-8BOM造成) 3.如何解决 3.11使用Notepad&#xff0c;选择“格式--以…

彻底理解ThreadLocal

转载自 彻底理解ThreadLocal 先总述&#xff0c;后分析 深挖过threadLocal之后&#xff0c;一句话概括&#xff1a;Synchronized用于线程间的数据共享&#xff0c;而ThreadLocal则用于线程间的数据隔离。所以ThreadLocal的应用场合&#xff0c;最适合的是按线程多实例&#xff…

云计算设计模式(四)——消费者的竞争模式

允许多个并发用户处理在同一个通讯通道接收的消息。这种模式使系统能够同时处理多个邮件&#xff0c;以优化吞吐量&#xff0c;提高可扩展性和可用性&#xff0c;以及平衡工作负载。 背景和问题 在云中运行的应用程序&#xff0c;可以预计&#xff0c;以处理大量的请求。而不是…

前后端分离趋势谈

最近已经不止一个人和我提起过vue了&#xff0c;在我的前端印象中&#xff0c;我还停留在smarty渲染模版&#xff0c;jquery做js处理。学了一晚上&#xff0c;对现在这种工程化webpack打包生成html&#xff0c;js&#xff0c;css的生产方式越来越有兴趣了。工作年限摆在这里的好…

云计算设计模式(五)——计算资源整合模式

合并多个任务或操作成一个单一的计算单元。这种模式可以提高计算资源的利用率&#xff0c;并降低与云托管的应用程序进行计算处理相关的成本和管理开销。 背景和问题 云应用程序频繁执行各种操作。在某些解决方案也可能是有意义的最初遵循的关注点分离的设计原则&#xff0c;并…

Visual Studio 2017 RC3支持.NET Core,延迟对Python的支持

Visual Studio 2017第三个候选版本上周发布&#xff0c;解决了之前发现的安装程序的小问题。由于这些问题得到了解决&#xff0c;现在值得关注的就是这次版本中更新了什么内容。&#xff08;版本是发布于1月27日的build 26127.00&#xff09; RC3版本中最值得关注的部分就是对N…

虚拟研讨会:.NET的未来在哪里?

.NET生态系统在过去的一年中发生了很多事情。在几个方面发展非常迅速&#xff1a;Xamari、UWP、.NET Core、.NET native、F#和开源等等。 如果要关注细节&#xff0c;那大的景象难以描绘。因为在每个方面都有新的动作&#xff1a;跨平台、云、移动、Web应用和通用应用。开发人员…