栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

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

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

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

JAVA类加载的 双亲委派机制,

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

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

一、背景知识
  • 类加载器

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

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

public abstract class ClassLoader {
    private final ClassLoader parent;
}

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

ExtClassLoader 和 AppClassLoader 都继承了 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;
   }
 }

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;
}

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

AppClassLoader 的 parent 属性,存的是 ExtClassLoader,

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

protected Class loadClass(String name, boolean resolve){
   // ExtClassLoader 中找对应的类	  
   c = parent.loadClass(name, false);
   if (c == null) {
       c = findClass(name);  // AppClassLoader 加载类
   }
   return c;
}

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

protected Class loadClass(String name, boolean resolve){
   // BootstrapClassLoader 中找对应的类
   c = findBootstrapClassOrNull(name);
   if (c == null) {
       c = findClass(name); // ExtClassLoader 加载类
   }
   return c;
}

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

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

  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);

最终调用的是 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);
        }
        // 省略其它代码
    }
  }

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

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

这行说明 loader 是 AppClassLoader,

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

最终是构造了 AppClassLoader 的 parent 属性。

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

虽然类加载是 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类");
	    }
	}

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

不能! 当然不能!!

虚拟机加载的String类,一定是 JDK 自带的。

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

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

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

五、如何打破双亲委派

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

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

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

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

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

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

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

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

六、小结

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

AppClassLoader 委托 ExtClassLoader 加载,

ExtClassLoader 又委托 BootstrapClassLoader 加载,

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

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

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

OVER

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/991739.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号