双亲委派模型

 双亲委派模型

 

 如上图中展示的类加载器之间的关系就是双亲委派模型.

双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应该有自己的父类加载器。

 双亲委派模型的工作过程

(1)从子类向父类依次查找是否以前加载过此类;加载过,返回以前加载的产物Class对象,没有加载过,委托父类进行查找。 当前类加载器从自己已经加载的类中查询是否此类已经加载,如果已经加载则返回原来已经加载的类。
(2)如果没有找到,就去委托父类加载器去加载。父类加载器也会采用同样的策略,查看自己已经加载过的类中是否包含这个类,有就返回,没有就委托其父类去加载,直到委托到启动类加载器为止。因为如果父类加载器为空了,就代表使用启动类加载器作为父加载器去加载该类。(也就是看到的String类加载器为null)
(3)如果启动类加载器加载失败,就会使用扩展类加载器来尝试加载,继续失败则会使用AppClassloader来加载,继续失败就会抛出一个异常ClassNotFoundException。

使用双亲委派模型的好处

(1)安全性,避免用户自己编写的类动态替换 Java 的一些核心类。如果不采用双亲委派模型的

加载方式进行 类的加载工作,那我们就可以随时使用自定义的类来动态替代 Java 核心 API 中定义的类。例如:如果黑客将“病毒 代码”植入到自定义的 String 类当中,随后类加载器将自定义的String 类加载到 JVM 上,那么此时就会对 JVM 产生 意想不到“病毒攻击。而双亲委派的这种加载方式就可以避免这种情况,因为 String 类已经在启动时就被启动类加载器进行了加载。
(2)避免类的重复加载,因为 JVM 判定两个类是否是同一个类,不仅仅根据类名是否相同进行判定,还需要判断加载该类的类加载器是否是同一个类加载器,相同的 class 文件被不同的类加载器加载得到的结果就是两个不同的类。

破坏双亲委派模型

第一次“被破坏”:

发生在双亲委派模型出现之前——即JDK1.2发布之前。类加载器和抽象类java.lang.ClassLoader则在JDK1.0时代就已存在,面对已经存在的用户自定义类加载器 的实现代码,Java设计者们引入双亲委派模型时不得不做出一些妥协。

虚拟机在进行类加载的时候会调用加载器的私有方法loadClassInternal(),而这个方法的而唯一逻辑就是去调用自己的loadClass()用户去继承java.lang.ClassLoader的唯一目的时为了重写loadClass()方法(loadClass()方法:双亲委派的逻辑就实现在这个方法之中)。

为了向前兼容,JDK1.2之后的java.lang,ClassLoader添加了一个protected方法findClass(),JDK 1.2之后已不提倡用户再去覆盖loadClass ()方法,而应当把自己的类加载逻辑写到findClass()方法中,在loadClass()方法的逻辑里如果父类加载失败,则会调用自己的findClassO方法来完成加载,这样就可以保证新写出来的类加载器是符合双亲委派规则。

第二次“被破坏”:

由模型自身缺陷导致,双亲委派模型很好的解决了各个类加载器的基础类的统一问题,基础类之所以被称为“基础”,是因为它们总是作为被用户代码调用的API, 但世事往往没有绝对的完美,如果基础类又要调用回用户的代码,那该怎么办了?

一个典型例子:JNDI服务,它的代码是由启动类加载器去加载,JNDI的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者(SPI, Service Provider Interface) 的代码,但启动类加载器不可能“认识”这些代码啊!那该怎么办?

线程上下文类加载器(Thread Context ClassLoader)。 这个类加载器可以通过java.lang.Thread 类的setContextClassLoaser()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个;如果在应用程序的全局范围内都没有设置过,那么这个类加载器默认就是应用程序类加载器。

JNDI服务使用这个线程上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型的一般性原则,但这也是无可奈何的事情。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。

第三次“被破坏”:
由于用户对程序动态性的追求导致的,即希望应用程序能够像电脑外设一样,鼠标有问题就换个鼠标,不用重启,不用停机。

在JSR-297D、JSR-277 。规范从纸上标准变成真正可运行的程序之前,OSGi是当前业界“事实上”的Java模块化标准,而OSGi实现模块化热部署的关键则是它自定义的类加载器机制的实现。每个程序模块(OSGi中称为Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。
在OSGi环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为网状结构,当收到类加载请求时,OSGi 将按照下面的顺序进行类搜索:
(1)将以java.*开头的类,委派给父类加载器加载。
(2)否则,将委派列表名单内的类,委派给父类加载器加载。
(3)否则,将Import列表中的类,委派给Export这个类的Bundle的类加载器
加载。
(4)否则,查找当前Bundle的ClassPath,使用自己的类加载器加载。
(5)否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment
Bundle的类加载器加载。
(6)否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器
加载。
(7)否则,类查找失败。
上面的查找顺序中只有开头两点仍然符合双亲委派规则,其余的类查找都是在平级
的类加载器中进行的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值