• JVM系列第二期——双亲委派和类加载器


    Java类生命周期:
    在这里插入图片描述
    这样设计符合面向对象的开闭原则和封装特性,JVM将类加载内部复杂的实现封装起来,拒绝上层开发者修改,只提供了一个拓展接口用于class文件二进制流的读取,上层开发人员用这个接口实现了动态代理,热部署等功能。
    在这里插入图片描述

    1.类加载器的分类

    在这里插入图片描述
    类加载器的分类属于JVM规范,是一种抽象概念,各个jvm的实现方式是不一定一样的,JVM规范中类加载器分为两部分,分为启动类加载器和非启动类加载器,这里只讲最常见的hotstop虚拟机,在hotstop中分为BOOTstrap类加载器和非BOOTstrap类加载器。
    在这里插入图片描述

    简单介绍一下这三种类加载器的区别,他们都继承自Java.lang.ClassLoader类,都可以作为对象被引用。我们平时写的代码都是用APPlication ClassLoader加载的,上面两种类加载器都只能从本地文件中获取字节码进行加载,而User ClassLoader可以获取任何来源的字节码并进行加载,也就是说,在java类加载机制中允许类从各个渠道获取二进制流进行加载。

    JVM中的类的加载器主要有三种:启动类加载器,拓展类加载器,应用类加载器。
         启动类加载器(Bootstrap classLoader):又称为引导类加载器,由C++编写,无法通过程序得到。主要负责加载JAVA中的                                                                  一些核心类库,主要是位于<JAVA_HOME>/lib/rt.jar中。
         拓展类加载器(Extension classLoader):主要加载JAVA中的一些拓展类,位于<JAVA_HOME>/lib/ext中,URLClassLoader的子类。                                                                 
         应用类加载器(System classLoader): 又称为系统类加载器,主要用于加载CLASSPATH路径下我们自己写的类,是拓展类 。 
         比如我们自己写了一个类Student类,经过编译后会得到Student.class文件,然后经过类加载器得到Class实例,例如通过
    
    Class.forName("com.***.Student"),通过全路径加载进来。然后我们用Student.class.getClassLoader()得到它的类加载器,得到的是AppClassLoader(即系统类加载器),如果用Student.class.getClassLoader().getParent()得到的是它的父加载器ExtClassLoader(即拓展类加载器),然后用Student.class.getClassLoader().getParent().getParent()得到将会是Null,因为启动类加载器是用C++写的,我们无法通过程序直接得到.
    
        常见问题:
    
        1.Object类是由哪个类加载器加载的?
    
          BootStrap ClassLoader
    
        2.我们自己写的类是由哪个类加载器加载的?
    
         System ClassLoader
    
       3.类加载器都是我们Java中的一个类ClassLoader的子类吗?
    
        BootStrap ClassLoader不是的,另外两个是的。
    
    类加载器的三大特性:委托性、可见性、单一性
    
    委托性:每个类中都有一个自己的类加载器的属性,这也就是为什么可以通过Student.class.getClassLoader()来    获取自己的类加载器。当一个类加载器要加载一个类时,它会先委托自己的父类加载器来加载,只有当父加载器无法加载类时,才会自己去加载。例如我们写了一个类Student,它的类加载器是System ClassLoader,它首先会委托给它的父加载器即Extension ClassLoader,然后Extension ClassLoader又会委托给它的父加载器BootStrap ClassLoader,启动类加载器无法加载这个类,交给拓展类加载器,拓展类加载器也无法加载,然后才轮到系统类加载器进行加载。
    可见性:可见性指的是父加载器无法利用子加载器加载的类,而子加载器可以利用父加载器加载的类。
    单一性:一个类只会被一个类加载器加载一次,不会被重复加载。
    我们自己也可以写自己的类加载器以满足自己特定的要求,只要实现ClassLoader这个类即可,但是要满足上面所说的类加载器的三种特性。
                                                                       
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    两个问题:
    在这里插入图片描述
    jvm规范中说:每个类加载器都有属于自己的命名空间。简单来说,即使用不同的类加载器加载了同一个限定名的类,JVM会认为这是两个不同的类,举个例子
    在这里插入图片描述
    首先使用匿名类的方式实现了UserClassLoader,重写了loadclass方法,(loadclass方法的内容就是直接从文件中读取class文件,然后调用父类的defineclass方法进行后续的加载操作,最后使用加载输出的class进行对象的实例化,并且对产生的对象进行类行判断)
    在这里插入图片描述
    这里可以看到对象的类行名是一样的,但结果输出的是false;因为我们自定义的类分别被APPlication类加载器和我们自定义的UserClassLoader加载了,JVM认为这是俩个完全不一样的类,这就验证了每个类加载器都有属于自己的命名空间。这就可能给程序的开发和维护带来很大的困扰,目前jvm有效的避免了这个问题,使用了双亲委派机制。
    面对上述情况我们现在的需求是:在默认情况下~
    在这里插入图片描述
    解释:在被动情况下,当一个类收到一个加载请求,他不会主动去加载,而是交给自己的父类加载,这样所有的类都会首先传递到最上层的Bootstrap ClassLoader,只有父亲加载器无法完成加载,儿子加载器才会尝试加载,(什么叫无法加载?就是根据类的限定名,类加载器没有在自己负责的加载路径中找到该类,注意,这里说的是父亲加载器儿子加载器,不是父类子类加载,这里的传递不是继承关系,是通过组合的方式实现的)

    2,双亲委派源码(双亲委派具体实现)

    在这里插入图片描述
    看java.lang.classloader中的findloaderclass方式,首先检车该类是否已经被加载了,如果没有就开启加载流程,如果有就直接读取缓存,parent变量代表当前Classloader的父亲加载器,如果parent==null,约定parent为bootstrap classloader,调用findbootstrapclassornull方法,让bootstrap classloader尝试加载,如果parent不为null,就让parent根据限定名尝试加载该类并返回class对象,如果返回的class对象是null,就说明parent没有能力加载这个类,就调用findclass,findclass表示如何去寻找该限定命名的class,需要各个类加载器自己实现。

    开始的两个问题:
    在这里插入图片描述

    3.破坏双亲委派模型

    3.1第一次破坏

    在这里插入图片描述
    双亲委派模型可以被破环,例如上图,可以重写java.lang.classloader中的loadclass方法,但双亲委派的逻辑就是存在这个方法里面的,这样双亲委派模型可以被破环。
    JVM的开发者为什么不把这个方法设置成final,禁止程序员重写呢?
    java.lang.classloader中的loadclass方法在jvm很早的版本就有了,双亲委派是jdk1.2引入的特性,java是向下兼容的,在引入双亲委派的时候,loadclass方法已经被重新很多次了,一点补救措施就是推荐使用findclass而不是直接重写loadclass方法。
    在这里插入图片描述

    3.2第二次破坏

    在这里插入图片描述
    举个例子:jdk想要提供操作数据库的功能,数据库有很多种,jdk不可能把操作所有数据库的代码都一一实现,(比较合理的是jdk提供一组规范(一组接口),各个不同的数据库厂商自己实现自己的类库,这里问题就出现了对jdk包中的加载肯定是使用的上层的类加载器,比如ootstrap ClassLoader,但当调用jdk的接口时候,接口所在的类将会引起第三方类库的加载,这就不符合自上而下的委派加载顺序了,会出现上层类加载器去调用下层类加载器的行为。

    3.3 第三次破环

    模块化热部署的关键是自定义类加载器机制,更换Bundle(程序模块)时,类加载器也会被换掉,然而类加载机制与双亲委派机制不同。

    思考题:

    在这里插入图片描述

    在这里插入图片描述

  • 相关阅读:
    比postman更好用的接口管理软件——Apifox
    HTML元素大全(1)
    元宇宙赛道加速破圈,UM Company区块链游戏抓住全球发展新风口
    ESXi 6.7添加螃蟹2.5g网卡支持
    Spring Cloud Alibaba 中 Nacos 组件的使用
    HarmonyOS-使用router事件跳转到指定UIAbility
    排序---堆排
    自建云服务计费系统
    java面试题-集合类面试题
    Linux常见命令总结
  • 原文地址:https://blog.csdn.net/qq_44543774/article/details/125455077