• 双亲委派——就是个唬人的翻译


    JAVA 类加载,鼎鼎有名的 “双亲委派机制”,

    名字很唬,听着很深奥,不知会吓退多少人。

    其实呢,就是一个简单的 递归调用,仅此而已。

    JAVA类加载的 双亲委派机制

    是逼格拉满的说法,大白话就是:

    如何通过递归方式,找到类的 class 文件,放到虚拟机。

    一、背景知识

    • 类加载器

    ClassLoader 这个类的实例,即所谓的类加载器,

    也就是说,类加载器,就是个对象

    public abstract class ClassLoader {
        private final ClassLoader parent;
    }
    
    • 1
    • 2
    • 3

    其中有个属性 parent ,这是个重点,后文详细说。

    ExtClassLoaderAppClassLoader 都继承了 ClassLoader
    在这里插入图片描述
    它俩的关系,是在调用 Launcher.getLauncher() 时,构造的类似链表的关系。

    除了这两个类加载器之外,还有个 BootstrapClassLoader

    它是虚拟机内嵌的,C++ 实现的。不归 java 管。

    这三个类加载器,加载的类包是不同的。

    1. BootstrapClassLoader :加载 jre/ 目录下的核心库
    2. ExtClassLoader :加载 /jre/lib/ext/ 目录下的扩展包
    3. AppClassLoader :加载 CLASSPATH 路径下的包

    二、类加载的双亲委派机制

    类加载时,先是 AppClassLoader 调用 loadClass 方法。

    为什么是不用 ExtClassLoader 调用,而用 AppClassLoader 调用!

    这个问题先放放,等会儿再说。

     protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{
       synchronized (getClassLoadingLock(name)) {         
           Class<?> c = findLoadedClass(name);
           if (c == null) {
               try {
                   if (parent != null) {
                       c = parent.loadClass(name, false);
                   } else {
                       c = findBootstrapClassOrNull(name);
                   }
               } catch (ClassNotFoundException e) {
               }
    
               if (c == null) {
                   c = findClass(name);
    
                   // this is the defining class loader; record the stats
                   sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                   sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                   sun.misc.PerfCounter.getFindClasses().increment();
               }
           }
           if (resolve) {
               resolveClass(c);
           }
           return c;
       }
     }
    
    • 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

    c = parent.loadClass(name, false); 这句表明是 递归调用,且只有两层嵌套。

    一个没有被加载过的类,加载的过程,画个图大概如下:

    在这里插入图片描述

    如果从代码中,你能想象出这个图,那直接跳到下个标题看。

    loadClass 方法,挑出重要的几行

    protected Class<?> loadClass(String name, boolean resolve){
        // 看下有没有被加载过,有的话直接返回
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            if (parent != null) {
                c = parent.loadClass(name, false);
            } else {
                c = findBootstrapClassOrNull(name);
            }
            // 如果没找到调用 findClass 查找
            if (c == null) {
                c = findClass(name);
            }
        }
        return c;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    假设一个类,从没有被加载过,findLoadedClass(name) 返回的就是 null,

    AppClassLoaderparent 属性,存的是 ExtClassLoader

    那它调用 loadClass 方法,简化就是

    protected Class<?> loadClass(String name, boolean resolve){
       // ExtClassLoader 中找对应的类	  
       c = parent.loadClass(name, false);
       if (c == null) {
           c = findClass(name);  // AppClassLoader 加载类
       }
       return c;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    ExtClassLoaderparent 属性,存的是 null,
    那它调用 loadClass 方法,简化就是

    protected Class<?> loadClass(String name, boolean resolve){
       // BootstrapClassLoader 中找对应的类
       c = findBootstrapClassOrNull(name);
       if (c == null) {
           c = findClass(name); // ExtClassLoader 加载类
       }
       return c;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    当代码拆开来看,是不是很简单。

    某个类,首次加载,代码执行的结果,一定是:

    1. BootstrapClassLoader 先加载试下,看能不能找到,找到结束,找不到继续
    2. ExtClassLoader 加载再试下,看能不能找到,找到结束,找不到继续
    3. AppClassLoader 加载再试下,看能不能找到,找到结束,找不到返回 null

    parents delegate 应该是原文,被翻译为 “双亲委派”。

    “双亲” 姑且理解为递归的两层嵌套吧,

    八成是一个不懂程序的人,强行给翻译的。

    也不是翻译的不好,就是让人看不懂。

    • findLoadedClass

    刚刚的分析,举例是一个从未被加载过的类,

    省掉了 findLoadedClass 这个方法的说明。

    它是查看下,虚拟机是否加载过某个类,如果加载过,直接返回。

      // First, check if the class has already been loaded
      Class<?> c = findLoadedClass(name);
    
      protected final Class<?> findLoadedClass(String name) {
          if (!checkName(name))
              return null;
          return findLoadedClass0(name);
      }
    
      private native final Class<?> findLoadedClass0(String name);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    最终调用的是 native 方法,不讲。

    不是我不想讲啊, native 方法我不会呀!

    换成俏皮话:臣妾做不到哇

    在这里插入图片描述

    大约理解为:虚拟机里有个类似缓存的东东,

    通过类的名字,可以拿到已加载过的类。

    总之一句话,不管 AppClassLoader 还是 ExtClassLoader

    加载类时,先查虚拟机是否加载过该类,

    如果加载过,直接返回,否则执行加载过程。

    三、AppClassLoader 的构建

    前面遗留了一个问题,类加载时,最先是 AppClassLoader 调用 loadClass 方法,

    为什么是这样呢? 看下 Launcher.getLauncher() 是怎么处理的。

    
    public class Launcher {
        private static Launcher launcher = new Launcher();
        private ClassLoader loader;
        public static Launcher getLauncher() {
            return launcher;
        }
        public Launcher() {
            Launcher.ExtClassLoader var1;
            try {
                var1 = Launcher.ExtClassLoader.getExtClassLoader();
            } catch (IOException var10) {
                throw new InternalError("Could not create extension class loader", var10);
            }
    
            try {
                this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
            } catch (IOException var9) {
                throw new InternalError("Could not create application class loader", var9);
            }
            // 省略其它代码
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    这段代码很清晰,调用 getLauncher 时,返回的 launcher 是初始化时,就确定了的。

    this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);

    这行说明 loaderAppClassLoader

    另外这行代码,将 var1 (ExtClassLoader ) 传进去的,

    最终是构造了 AppClassLoaderparent 属性。

    代码不贴出来了,有兴趣自己看吧。

    虽然类加载是 AppClassLoader 开始的,

    但查找却是从 BootstrapClassLoader 依次往下的。

    为什么会这样设计呢?

    四、双亲委派的安全机制

    String 类,Object 类都是 BootstrapClassLoader 加载的。

    有没有可能,我自己建一个目录,写一个 java.lang.String

    让虚拟机加载我的这个String 呢?比如下面这样

    	package java.lang;
    	
    	public class String {
    	    public static void main(String[] args) {
    	        System.out.println("自创String类");
    	    }
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    当然是可以写的,但想想,它能运行成功吗?

    不能! 当然不能!!
    在这里插入图片描述
    虚拟机加载的String类,一定是 JDK 自带的。

    为啥?因为双亲委派啊。随随便便就被你给替换了,那还了得。

    双亲委派保证了类加载的唯一性,

    同时也保证了核心类库不被篡改。

    五、如何打破双亲委派

    这也是个逼格拉满的说法。换个普通的说法就是:

    自己写个类,继承 ClassLoader ,重写 loadClass 方法。

    具体的细节,本篇不说了。

    顺便说一句,自己写的类加载器,继承 ClassLoader,后,

    这个自己写的 类,其 parent 属性,是 AppClassLoader

    为什么这样,问度娘吧,实在不行,就留言。

    绝大多数人,不关心这个,要解释,还得贴一堆代码。

    这个也不重要,知道就行了。

    六、小结

    所谓双亲委派的类加载机制是:

    AppClassLoader 委托 ExtClassLoader 加载,

    ExtClassLoader 又委托 BootstrapClassLoader 加载,

    人家给加载了,你就不用加载,

    人家加载不到,你就自己加载,

    结果就是各司其职,各自办好份内的事。

    OVER

  • 相关阅读:
    第三课-软件升级-Stable Diffusion教程
    设计模式之抽象工厂模式
    初见物理引擎库Cannon.js:与Cesium的整合
    华为某员工爆料:偷偷跑出去面试,被面试官鄙视了。第一句话就问:华为淘汰的吧,35岁了,这个年龄在华为能混得下去吗?身体没啥毛病吧
    JAVA&Android实现MQTT上位机软件功能-订阅主题与发布主题
    长安链GO语言智能合约环境搭建及使用
    倍福tnzip,tszip,tpzip文件的打开方式
    算法题解记录29+++全排列(百日筑基)
    流媒体服务器
    leetcode 二分查找·系统掌握 寻找旋转排序数组中的最小值II
  • 原文地址:https://blog.csdn.net/every__day/article/details/125361048