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

2233f71d726460eb486d51a51979cd96.png

1、工作流程

50f2365c5e40ee6e04b3ce43a3ea8573.png


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

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

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

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

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

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

双亲委派模型示意图 

7848e2fe79bcbfbb0c92e2f5c59f6dca.png

980c26c5d4f1fa5e4d6530c0f1f409cb.png


双亲委派模型的优点

747a273840b182be81bffda36c2582d2.png


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

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


7f53bc3e2f208d8dc5fd240e0ae8ea82.png

2、双亲委派模型的实现

8ae235f3cc683f614e799a740e2a9aa1.png

7b6687d8f9c923c70019d119066cdb16.png


2e9c0cb355977aa1e471613223983259.png

类加载的方式

a1925c2be31eff14e46d46059e1ed8d9.png


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

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

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


ab67dd00b8e4e575b93ae7b308048f2a.png

3、自定义类加载器

3dab36939b7b0f9af6c1a6e41fe5d55d.png

实现方式

781f95f04442603ada39b45075fc3ab6.png


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

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

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

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

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

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

4463a657f4a0c41ce4779e9cd14822e8.png

利用URLClassLoader进行加载

1d5dc33b922b8c5e76e9daef6cf107f1.png


33d3a2e573ddbd26b1a783cbdadf3496.png

4、类加载实例

284e449c7b5494813a24a6b878f5e262.png

命令行下执行HelloWorld.java

b48ab9e81c225de7ab50cf28760f82ea.png

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

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

  2. 产生一个Bootstrap ClassLoader

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

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

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

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

5edc69ec614f8f70af0b96aa24537938.png

6bd7c310ec6d6f26d3a0a13db103c387.png

5、破坏双亲委派模型

01673542145baf5c70c010289277357c.png

双亲模型的问题

af46a0b5a59aeb90a6925c793b21537b.png

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

JDBC

a00c55ec05c639bcea15c707870c36d5.png

如下证明 JDBC 是启动类加载器加载,但 mysql 驱动是应用类加载器。而 JDBC 运行时又需要去访问子类加载器加载的驱动,就破坏了该模型。 74544d9b17c0f19dce42deab8ff293fd.png JDK 自己为解决该问题,引入线程上下问类加载器,可以通过Thread的setContextClassLoader()进行设置

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


热替换

c10a1e3b186d8b92f5c2002064459f9b.png


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


3b5ad004f22c0660cb4958a4ccc97ae4.png

6、FAQ

62412189ab1b81beed05fcdb90e3a0b1.png


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

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

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

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值