双亲委派模型机制
创始人
2024-02-07 23:45:16

文章目录

  • 类加载器
  • findClass
  • loadClass
  • JDK双亲委派的破坏
    • 第一次破坏
    • 第二次破坏
  • tomcat
  • 热部署
  • JDK9最新改动

双亲委派机制是当类加载器需要加载某一个.class字节码文件时,则首先会把这个任务委托给他的上级类加载器,递归这个操作,如果上级没有加载该.class文件,自己才会去加载这个.class。主要目的是为了安全,保证一个Java类在JVM的唯一性

类加载器

在这里插入图片描述

  • Bootstrap ClassLoader(启动类加载器):该类加载器由C++实现的。负责加载Java基础类,对应加载的文件是%JRE_HOME/lib/ 目录下的rt.jar、resources.jar、charsets.jar和class等。
  • Extension ClassLoader(标准扩展类加载器):继承URLClassLoader。对应加载的文件是%JRE_HOME/lib/ext 目录下的jar和class等。
  • App ClassLoader(系统类加载器):继承URLClassLoader。对应加载的应用程序classpath目录下的所有jar和class等。
  • CustomClassLoader(用户自定义类加载器):由Java实现。我们可以自定义类加载器,并可以加载指定路径下的class文件

类加载器的父子关系不是通过继承来实现的,比如AppClassLoader并非ExtClassLoader的子类,只是AppClassLoader的parent指向ExtClassLoader对象。
所以若自定义类加载器,不是去继承AppClassLoader,而是继承ClassLoader抽象类,再重写findClass和loadClass即可

findClass

作用是根据类的路径信息,查找类,再调用defineClass 把类字节数组生成class,如果需要自定义ClassLoader的话,必须重写该方法。不打破双亲委派机制

protected Class findClass(final String name)throws ClassNotFoundException{final Class result;try {result = AccessController.doPrivileged(new PrivilegedExceptionAction>() {public Class run() throws ClassNotFoundException {String path = name.replace('.', '/').concat(".class");Resource res = ucp.getResource(path, false);if (res != null) {try {return defineClass(name, res);} catch (IOException e) {throw new ClassNotFoundException(name, e);}} else {return null;}}}, acc);} catch (java.security.PrivilegedActionException pae) {throw (ClassNotFoundException) pae.getException();}if (result == null) {throw new ClassNotFoundException(name);}return result;}

loadClass

加载类,父类不能加载时,由当前的类加载器加载,会调用findClass方法
若要打破双亲委派机制,重写loadClass

    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); //委托父加载器} else {c = findBootstrapClassOrNull(name);  //ext类加载器加载器才会走到该分支,尝试让最顶层的BootstraptClassLoader加载}} catch (ClassNotFoundException e) {//如果父加载器无法加载,会抛出 ClassNotFoundException}if (c == null) {// 父类不能加载,由当前的类加载器加载long t1 = System.nanoTime();c = findClass(name);   //需要自己加载类时,调用findClass// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) { //如果要求立即链接,那么加载完类直接链接resolveClass(c);}return c;}}

JDK双亲委派的破坏

第一次破坏

在 jdk 1.2 之前,那时候还没有双亲委派模型,不过已经有了 ClassLoader 这个抽象类,所以已经有人继承这个抽象类,重写 loadClass 方法来实现用户自定义类加载器。

而在 1.2 的时候要引入双亲委派模型,为了向前兼容, loadClass 这个方法还得保留着使之得以重写,新搞了个 findClass 方法让用户去重写,并呼吁大家不要重写 loadClass 只要重写 findClass。

这就是第一次对双亲委派模型的破坏,因为双亲委派的逻辑在 loadClass 上,但是又允许重写 loadClass,重写了之后就可以破坏委派逻辑了

第二次破坏

第二次破坏指的是 JNDI、JDBC 之类的情况。

首先得知道什么是 SPI(Service Provider Interface),它是面向拓展的,也就是说我定义了个规矩,就是 SPI ,具体如何实现由扩展者实现。

像我们比较熟的 JDBC 就是如此。

MySQL 有 MySQL 的 JDBC 实现,Oracle 有 Oracle 的 JDBC 实现,我 Java 不管你内部如何实现的,反正你们这些数据库厂商都得统一按我这个来,这样我们 Java 开发者才能容易的调用数据库操作,所以在 Java 核心包里面定义了这个 SPI。

而核心包里面的类都是由启动类加载器去加载的,但它的手只能摸到\lib或Xbootclasspath指定的路径中,其他的它鞭长莫及。

而 JDBC 的实现类在我们用户定义的 classpath 中,只能由应用类加载器去加载,所以启动类加载器只能委托子类来加载数据库厂商们提供的具体实现,这就违反了自下而上的委托机制。

具体解决办法是搞了个线程上下文类加载器,通过setContextClassLoader()默认情况就是应用程序类加载器,然后利用Thread.current.currentThread().getContextClassLoader()获得类加载器来加载。

这就是第二次破坏双亲委派模型

tomcat

打破双亲委派

热部署

为了满足热部署的需求,进行不停机更新。

OSGI (开放服务网关协议,Open Service Gateway Initiative)技术是Java动态化模块化系统的一系列规范。

OSGI 就是利用自定义的类加载器机制来完成模块化热部署,而它实现的类加载机制就没有完全遵循自下而上的委托,有很多平级之间的类加载器查找。

JDK9最新改动

在这里插入图片描述

JDK 9 为了模块化的支持,对双亲委派模式做了一些改动:

  • 扩展类加载器被平台类加载器(Platform ClassLoader)取代。

JDK 9 时基于模块化进行构建(原来的 rt.jar 和 tools.jar 被拆分成数十个 JMOD 文件), 其中的 Java
类库就已天然地满足了可扩展的需求,那自然无须再保留 \lib\ext 目录,此前使用这个目录或者 java.ext.dirs 系统变量来扩展 JDK 功能的机制已经没有继续存在的价值了。

  • 平台类加载器和应用程序类加载器都不再继承自 java.net.URLClassLoader。

现在启动类加载器、平台类加载器、应用程序类加载器全都继承于 jdk.internal.loader.BuiltinClassLoader。

如果有程序直接依赖了这种继承关系,或者依赖了 URLClassLoader 类的特定方法,那代码很可能会在 JDK 9 及更高版本的 JDK 中崩溃。

  • 启动类加载器现在是在 Java 虚拟机内部和 Java 类库共同协作实现的类加载器(以前是 C++实现)。

为了与之前的代码保持兼容,所有在获取启动类加载器的场景(譬如 Object.class.getClassLoader)中仍然会返回 null来代替,而不会得到 BootClassLoader 的实例。

  • 类加载的委派关系也发生了变动。
    当平台及应用程序类加载器收到类加载请求,在委派给父加载器加载前,要先判断该类是否能够归属到某一个系统模块中,如果可以找到这样的归属关系,就要优先委派给负责那个模块的加载器完成加载。

在 Java 模块化系统明确规定了三个类加载器负责各自加载的模块:

  • 启动类加载器负责加载的模块
java.base                        java.security.sasl
java.datatransfer                java.xml
java.desktop                     jdk.httpserver
java.instrument                  jdk.internal.vm.ci
java.logging                     jdk.management
java.management                  jdk.management.agent
java.management.rmi              jdk.naming.rmi
java.naming                      jdk.net
java.prefs                       jdk.sctp
java.rmi                         jdk.unsupported
  • 平台类加载器负责加载的模块
java.activation*                jdk.accessibility
java.compiler*                  jdk.charsets
java.corba*                     jdk.crypto.cryptoki
java.scripting                  jdk.crypto.ec
java.se                         jdk.dynalink
java.se.ee                      jdk.incubator.httpclient
java.security.jgss              jdk.internal.vm.compiler*
java.smartcardio                jdk.jsobject
java.sql                        jdk.localedata
java.sql.rowset                 jdk.naming.dns
java.transaction*               jdk.scripting.nashorn
java.xml.bind*                  jdk.security.auth
java.xml.crypto                 jdk.security.jgss
java.xml.ws*                    jdk.xml.dom
java.xml.ws.annotation*         jdk.zipfs
  • 应用程序类加载器负责加载的模块
jdk.aot                         jdk.jdeps
jdk.attach                      jdk.jdi
jdk.compiler                    jdk.jdwp.agent
jdk.editpad                     jdk.jlink
jdk.hotspot.agent               jdk.jshell
jdk.internal.ed                 jdk.jstatd
jdk.internal.jvmstat            jdk.pack
jdk.internal.le                 jdk.policytool
jdk.internal.opt                jdk.rmic
jdk.jartool                     jdk.scripting.nashorn.shell
jdk.javadoc                     jdk.xml.bind*
jdk.jcmd                        jdk.xml.ws*
jdk.jconsole

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...