看完她再说你懂双亲委派

11 篇文章 0 订阅


前言:如果下面的这些问题你都知道,那么就不需要看了

Tip:同道中人:能看到这,证明你其实是一个有追求的程序猿,点赞,收藏转发一波呗,感激不尽

1、什么是双亲委派?
2、为什么需要双亲委派,不委派有什么问题?
3、"父加载器"和"子加载器"之间的关系是继承的吗?
4、双亲委派是怎么实现的?
5、我能不能主动破坏这种双亲委派机制?怎么破坏?
6、为什么重写loadClass方法可以破坏双亲委派,这个方法和findClass()区别是什么?
7、说一说你知道的双亲委派被破坏的例子吧
8、为什么JDBC等需要破坏双亲委派?
9、为什么TOMCAT要破坏双亲委派?


一、什么是双亲委派?

众所周知,虚拟机是通过类加载器加载你的class文件的,来保证功能的正常运行,而在java中其实有好多加载器的,那么说到这里,你要明白的是由哪些,作用,以及加载流程。一旦加载个class是用哪个加载器加载的,而另一个class为啥不是这个加载器加载的。这些种种的原因都是因为双亲委派机制的存在。

首先,我们需要知道的是,Java语言系统中支持以下4种类加载器:
Bootstrap ClassLoader 启动类加载器
Extention ClassLoader 标准扩展类加载器
Application ClassLoader 应用类加载器
User ClassLoader 用户自定义类加载器
这四种类加载器之间,是存在着一种层次关系的,如下图

在这里插入图片描述
自下而上,每个加载器都是下面加载器的父级加载器,当然BootStrapClassLoader 加载器是根加载器,没有父级。
看到这里,引入双亲委派的这个说明,其实就是jvm加载一个类的时候,不是直接就加载,而是往上级抛,依次抛到最顶层,如果最顶层的加载器找不到,则下一级继续找,也就是说,有些供父类加载器加载的。看到这里大家会想,那么什么时候父加载器能加载,而什么时候加载不了呢?
其实,Java中提供的这四种类型的加载器,是有各自的职责的:
Bootstrap ClassLoader ,主要负责加载Java核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。
Extention ClassLoader,主要负责加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。
Application ClassLoader ,主要负责加载当前应用的classpath下的所有类
User ClassLoader , 用户自定义的类加载器,可加载指定路径的class文件
那么也就是说,一个用户自定义的类,如com.itcode.Yuanzhicun.class 是无论如何也不会被Bootstrap和Extention加载器加载的。

二、为什么需要双亲委派?

这种机制有几个好处。
首先,通过委派的方式,可以避免类的重复加载,当父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类。
另外,通过双亲委派的方式,还保证了安全性。因为Bootstrap ClassLoader在加载的时候,只会加载JAVA_HOME中的jar包里面的类,如java.lang.String,你自己写的String类一般来说是加载不进去的,
那么,就可以避免有人自定义一个有破坏功能的java.lang.String被加载。这样可以有效的防止核心Java API安全。

三、双亲委派是怎么实现的?

代码不难理解,主要就是以下几个步骤:
1、先检查类是否已经被加载过
2、若没有加载则调用父加载器的loadClass()方法进行加载
3、若父加载器为空则默认使用启动类加载器作为父加载器。
4、如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。

代码如下(示例):

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        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;
        }
    }

四、父子加载器"之间的关系是继承吗?

这个一般来说只是了解下,别听着说父加载器什么什么的,就是继承关系,其实不是的,代码结构是组合
在这里插入图片描述

五、原理都清楚了,那么如何打破双亲委派呢?

其实就打破双亲委派这个机制太简单了,不说代码层面,就是逻辑理解上想:不让类交由上级处理就好了,其实在代码处理上大家还要了解下 loadClass()、findClass()这两个方法。
loadClass() 就是主要进行类加载的方法,默认的双亲委派机制就实现在这个方法中
findClass() 根据名称或位置加载.class字节码(父类只是抛出一个异常信息,具体加载逻辑交由子类实现们,用于自定义加载器加载指定路径文件)
其实大家看上面的loadclass()方法的源码就知道了,如果上级加载器都加载不到,才交由自己加载走的是 findClass方法()。看到这里不就清楚了,想要破坏这种机制,那么就自定义一个类加载器,重写其中的loadClass方法,使其不进行双亲委派即可。
如果我们想定义一个类加载器,但是不想破坏双亲委派模型的时候呢
可以继承ClassLoader,并且重写findClass方法。findClass()方法是JDK1.2之后的ClassLoader新添加的一个方法。

六、双亲委派被破坏的例子

其实双亲委派被破坏的例子很多,jdbc,Tomcat等就没有遵循双亲委派的加载机制。
这里我先讲讲为啥要破坏,后面会介绍jdbc,Tomcat打破的点在哪。上面也提到了jdk1.2才引入的双亲委派,也就是说之前的肯定没有,机制吧?以后有人问你,拿这句话回答完美
؏؏☝ᖗ乛◡乛ᖘ☝؏؏

jdbc打破双亲委派其实是一个代表性的例子:需要加载SPI接口实现类的情况,不知道大家了解SPI吗?不了解的话可以 传送门:springboot自定义starter,spi也会了springboot的starter也掌握了,一举两得,在加一得,其实 dubbox也是借鉴jdk 的 spi机制。

还有就是 web 容器 Tomcat典型的代表,热部署 都是打破了双亲委派才得以实现功能

七、 为什么需要打破双亲委派

JDBC为什么打破双亲委派

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql", "root", "1234");

上面这行代码大家肯定熟悉,其实他就用到了spi机制。一般来说我们通过Java 的API调用相关基础类都是 BootStarp加载的,jdbc也是,这里简单介绍下jdbc,jdbc其实就一个 规范(接口),提供给各个数据库厂商接入java提供的,所以这个API肯定是由 BootStarp加载的,所以在getConnection代码执行之前,DriverManager会先被类加载器加载,因为java.sql.DriverManager类是位于rt.jar下面的 ,所以他会被根加载器加载。

类加载时,会执行该类的静态方法。其中有一段关键的代码是:
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
这段代码,会尝试加载classpath下面的所有实现了Driver接口的实现类。
那么,问题就来了。
DriverManager是被根加载器加载的,那么在加载时遇到以上代码,会尝试加载所有Driver的实现类,但是这些实现类基本都是第三方提供的,根据双亲委派原则,第三方的类不能被根加载器加载。
那么,怎么解决这个问题呢?
于是,就在JDBC中通过引入ThreadContextClassLoader(线程上下文加载器,默认情况下是AppClassLoader)的方式破坏了双亲委派原则。
我们深入到ServiceLoader.load方法就可以看到:
在这里插入图片描述
为什么Tomcat要破坏双亲委派
我们知道,Tomcat是web容器,那么一个web容器可能需要部署多个应用程序。
不同的应用程序可能会依赖同一个第三方类库的不同版本,但是不同版本的类库中某一个类的全路径名可能是一样的。
如多个应用都要依赖moran.jar,但是A应用需要依赖1.0.0版本,但是B应用需要依赖1.0.1版本。这两个版本中都有一个类是com.moran.Test.class。
如果采用默认的双亲委派类加载机制,那么是无法加载多个相同的类。
所以,Tomcat破坏双亲委派原则,提供隔离的机制,为每个web容器单独提供一个WebAppClassLoader加载器。
Tomcat的类加载机制:为了实现隔离性,优先加载 Web 应用自己定义的类,所以没有遵照双亲委派的约定,每一个应用自己的类加载器——WebAppClassLoader负责加载本身的目录下的class文件,加载不到时再交给CommonClassLoader加载,这和双亲委派刚好相反。

还有就是jsp页面动态刷新,热部署的功能也是基于打破这种机制的,否则加载器缓存了,就只加载一次旧数据


总结

看到这里相信大家都对双亲委派有了进一步的认知了吧

写在最后,感谢点赞关注收藏转发

欢迎关注我的微信公众号 【猿之村】
在这里插入图片描述

来聊聊Java面试
加我的微信进一步交流和学习,微信手动搜索
【codeyuanzhicunup】添加即可
如有相关技术问题欢迎留言探讨,公众号主要用于技术分享,包括常见面试题剖析、以及源码解读、微服务框架、技术热点等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值