JVM进阶之双亲委派机制

Posted 编程小吉

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM进阶之双亲委派机制相关的知识,希望对你有一定的参考价值。

双亲委派机制

1.概述

如果一个类加载器在接收到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父加载器去完成。依次递归,如果父加载器可以完成类加载任务,就成功返回。所以只有父加载器无法完成此加载任务时,才会由自己去加载。

2.本质

等同于规定了类加载的顺序是:引导类加载器先加载,扩展类加载器次加载,最后才会由系统类加载器加载。

3.实现

双亲委派机制在java.lang.ClassLoadelr.loadClass方法中体现,过程如下:

  1. 先在当前类加载器的缓存中查找有无目标类,如果有,直接返回。
  2. 判断当前加载器的父加载器是否为空,如果不为空,则调用parent.loadClass方法进行加载。
  3. 如果当前加载器的父类加载器为空,则调用findBootstrapClassOrNull方法,让引导类加载器进行加载。
  4. 如果以上3条路径都没能成功加载,则调用findClass方法进行加载。

4.优点

  • 避免类的重复加载,确保类的全局唯一性
  • 保护程序安全,防止核心API被随意篡改

5.弊端

检查类是否加载的委托过程是单向的,这个方式虽然从结构上说比较清晰,但使得顶层的ClassLoader无法访问底层的ClassLoader所加载的类。

6.注意细节

  • 即使我们重写java.lang.ClassLoader.loadClass方法,然后抹去其中的双亲委派机制,我们也不能覆盖JDK中的核心API类库。因为JDK还为核心类库提供了一层保护机制,不管是自定义的类加载器,还是系统类加载器等等,最终都必须调用preDefineClass方法,该方法中提供了对JDK核心类库的保护。
  • JVM规范并没有明确要求类加载器的加载机制一定要使用双亲委派模型,只是建议采用这种方式而已。 比如在Tomcat中,类加载器所采用的加载机制就和传统的双亲委派模型有一定区别,当缺省的类加载器接收到一个类的加载任务时,首先会由它自行加载,当它加载失败时,才会将类的加载任务委派给它的超类加载器去执行。

7.破坏示例

  • 双亲委派模型第一次“被破坏”发生在JDK1.2之前。

    因为双亲委派机制是在JDK1.2之后推出的,所以之前没有双亲委派机制。

  • 双亲委派模型第二次“被破坏”是由它自身存在的缺陷所导致的。

    • 在Java中,JNDI是一个标准服务,它是由启动类加载器来完成加载的,但它存在的目的是对资源进行查找和集中管理,所以它需要调用由其他厂商实现并部署在应用程序的下的JNDI服务提供者接口。

    • 如果使用双亲委派机制,那么启动类加载器根本无法调用这些实现接口方法,所以Java的设计团队设计了线程上下文类加载器,这个类加载器可以通过Thread类的setContextClassLoader方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。

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

  • 双亲委派模型第三次“被破坏”是由于用户对程序动态性的追求而导致的。

    以OSGI为例,它主要是实现模块化热部署的,每一个程序模块都有一个自己的类加载器,当需要更换一个程序模块时,就把该模块连同类加载器一起换掉以实现代码的热替换。在OSGi环境下,类加载器不再使用双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构。

8.热替换

  • 热替换是指在程序的运行过程中,不停止服务,只通过替换程序文件来修改程序的行为。热替换的关键需求在于服务不能中断,修改必须立即表现正在运行的系统之中。基本上大部分脚本语言都是天生支持热替换的,比如:php,只要替换了PHP源文件,这种改动就会立即生效,而无需重启Web服务器。

  • 在Java中,如果一个类已经加载到了系统中,那么通过修改类文件并无法让系统再此加载并重定义这个类,因为所有类都只会被加载一遍。

  • 不过在Java中实现这一功能的一个可行的方法就是灵活运用ClassLoader,然后自定义一个类加载器,去不断的加载此类。

    package com.example;
    
    public class Demo1 
        public void hot() 
            // 修改之前
    		System.out.println("OldDemo1");
            // 修改之后
    		System.out.println("OldDemo1---> NewDemo1");
        
    
    
    public class LoopRun 
        public static void main(String args[]) 
            while (true) 
                try 
                    // 创建自定义类加载器的实例
                    MyClassLoader loader = new MyClassLoader("ClassDir");
                    // 加载类
                    Class clazz = loader.findClass("com.example.Demo1");
                    
                    // 创建类的实例
                    Object demo = clazz.newInstance();
                    
                    // 调用类的方法
                    Method m = clazz.getMethod("hot");
                    m.invoke(demo);
                    
                    // 每隔5秒打印一次
                    Thread.sleep(5000);
                 catch (Exception e) 
                    e.printStackTrace();
                
            
        
    
    

    修改之前输出

    修改之后输出

以上是关于JVM进阶之双亲委派机制的主要内容,如果未能解决你的问题,请参考以下文章

JVM进阶之双亲委派机制

JVM系列:Java类加载机制之双亲委派模型

JVM类的加载机制之双亲委派

JVM:类加载与类加载机制(双亲委派等)

JVM笔记二双亲委派机制

双亲委派机制