java破坏双亲委派_JDK为何自己先破坏双亲委派模型?

点击上方“JavaEdge”,关注公众号

设为“星标”,好文章不错过!

说是双亲,其实多级单亲,无奈迎合历史的错误翻译吧。

9e088814751df648c06a8dd3175fc7f5.png

1 工作流程

a7e6216b6cd30b001f62967d4e320a27.png

当一个类加载器收到一个类加载请求 在 JDK9 后,会首先搜索它的内建加载器定义的所有“具名模块”:

如果找到合适的模块定义,将会使用该加载器来加载

如果未找到,则会将该请求委派给父级加载器去加载

因此所有的类加载请求最终都应该被传入到启动类加载器(Bootstrap ClassLoader)中,只有当父级加载器反馈无法完成这个列的加载请求时(它的搜索范围内不存在这个类),子级加载器才尝试加载。

在类路径下找到的类将成为这些加载器的无名模块。

这里的父子关系是组合而不是继承。

双亲委派模型示意图

431d987982c0ac45d2c9f9dab8ec4f0d.png

2b24540a6b7f03b45ce5601d12286b36.png

双亲委派模型的优点

431d987982c0ac45d2c9f9dab8ec4f0d.png

避免重复加载 父类已经加载了,子类就不需要再次加载。eg,object 类。它存放在 rt.jar 中,无论哪个类加载器要加载这个类,最终都是委派给处于模型顶端的启动类加载器加载,因此 object 类在程序的各种加载环境中都是同一个类。

更安全 解决了各个类加载器的基础类的统一问题,如果不使用该种方式,那么用户可以随意定义类加载器来加载核心 API,会带来安全隐患。

9e088814751df648c06a8dd3175fc7f5.png

双亲委派模型的实现

a7e6216b6cd30b001f62967d4e320a27.png

983fd469f4d5e5c5e94c23d66cdb3312.png

9e088814751df648c06a8dd3175fc7f5.png

类加载的方式

a7e6216b6cd30b001f62967d4e320a27.png

通过命令行启动应用时由JVM初始化加载含有main()方法的主类。

通过Class.forName()方法动态加载,会默认执行初始化块(static{}),但是Class.forName(name,initialize,loader)中的initialze可指定是否要执行初始化块。

通过ClassLoader.loadClass()方法动态加载,不会执行初始化块。

9e088814751df648c06a8dd3175fc7f5.png

自定义类加载器

a7e6216b6cd30b001f62967d4e320a27.png

实现方式

431d987982c0ac45d2c9f9dab8ec4f0d.png

遵守双亲委派模型 继承ClassLoader,重写findClass()方法。

破坏双亲委派模型 继承ClassLoader,重写loadClass()方法。

通常我们推荐采用第一种方法自定义类加载器,最大程度上的遵守双亲委派模型。

如果有一个类加载器能加载某个类,称为定义类加载器,所有能成功返回该类的Class的类加载器都被称为初始类加载器。

自定义类加载的目的是想要手动控制类的加载,那除了通过自定义的类加载器来手动加载类这种方式,还有其他的方式么?

利用现成的类加载器进行加载

abc8e3c6ea9d0fbcc4eb6550472e36dd.png

利用URLClassLoader进行加载

23a48f16a5c932917b7598a7c7d3988b.png

9e088814751df648c06a8dd3175fc7f5.png

类加载实例

a7e6216b6cd30b001f62967d4e320a27.png

命令行下执行HelloWorld.java

41bf511d2453f4e34758175c675ab740.png

该段代码大体经过了一下步骤:

寻找jre目录,寻找jvm.dll,并初始化JVM

产生一个Bootstrap ClassLoader

Bootstrap ClassLoader加载器会加载他指定路径下的java核心api,并且生成Extended ClassLoader加载器的实例,然后Extended ClassLoader会加载指定路径下的扩展java api,并将其父设置为Bootstrap ClassLoader

Bootstrap ClassLoader生成Application ClassLoader,并将其父Loader设置为Extended ClassLoader

最后由AppClass ClassLoader加载classpath目录下定义的类——HelloWorld类。

我们上面谈到 Extended ClassLoader和Application ClassLoader是通过Launcher来创建,现在我们再看看源代码

338502267a1b89e9e67e31675ac4e575.png

9e088814751df648c06a8dd3175fc7f5.png

破坏双亲委派模型

a7e6216b6cd30b001f62967d4e320a27.png

双亲模型的问题

431d987982c0ac45d2c9f9dab8ec4f0d.png

父加载器无法向下识别子加载器加载的资源。

JDBC

431d987982c0ac45d2c9f9dab8ec4f0d.png

如下证明 JDBC 是启动类加载器加载,但 mysql 驱动是应用类加载器。而 JDBC 运行时又需要去访问子类加载器加载的驱动,就破坏了该模型。

e078da39804dde0f214ffdd19e7a41b0.pngJDK 自己为解决该问题,引入线程上下问类加载器,可以通过Thread的setContextClassLoader()进行设置

当为启动类加载器时,使用当前实际加载驱动类的类加载器

f3dd674c1661a2027574ce2c737aa767.png

热替换

431d987982c0ac45d2c9f9dab8ec4f0d.png

比如OSGI的模块化热部署,它的类加载器就不再是严格按照双亲委派模型,很多 可能就在平级的类加载器中执行了。

9e088814751df648c06a8dd3175fc7f5.png

FAQ

a7e6216b6cd30b001f62967d4e320a27.png

ClassLoader通过一个类全限定名来获取二进制流,如果我们需通过自定义类加载其来加载一个Jar包的时候,难道要自己遍历jar中的类,然后依次通过ClassLoader进行加载吗?或者说我们怎么来加载一个jar包呢? 对于动态加载jar而言,JVM默认会使用第一次加载该jar中指定类的类加载器作为默认的ClassLoader。

假设我们现在存在名为sbbic的jar包,该包中存在ClassA和ClassB类(ClassA中没有引用ClassB)。现在我们通过自定义的ClassLoaderA来加载在ClassA这个类,此时ClassLoaderA就成为sbbic.jar中其他类的默认类加载器。即ClassB默认也会通过ClassLoaderA去加载。

如果一个类引用的其他的类,那么这个其他的类由谁来加载?

如果ClassA中引用了ClassB呢? 当类加载器在加载ClassA的时候,发现引用了ClassB,此时类加载如果检测到ClassB还没有被加载,则先回去加载。当ClassB加载完成后,继续回来加载ClassA。即类会通过自身对应的来加载其加载其他引用的类。

既然类可以由不同的加载器加载,那么如何确定两个类如何是同一个类?

JVM规定:对于任何一个类,都需要由加载它的类加载器和这个类本身一同确立在java虚拟机中的唯一性。即在jvm中判断两个类是否是同一个类取决于类加载和类本身,也就是同一个类加载器加载的同一份Class文件生成的Class对象才是相同的,类加载器不同,那么这两个类一定不相同。

197dadc40c89782051a93665f343b932.gif

目前交流群已有800+人,旨在促进技术交流,可关注公众号添加笔者微信邀请进群

喜欢文章,点个“在看、点赞、分享”素质三连支持一下~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值