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

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

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

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

JAVA类加载的 双亲委派机制

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

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

一、背景知识

  • 类加载器

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

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

public abstract class ClassLoader {
    private final ClassLoader parent;
}

其中有个属性 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;
   }
 }

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,

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

ExtClassLoaderparent 属性,存的是 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);

这行说明 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类");
	    }
	}

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

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

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

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

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

五、如何打破双亲委派

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

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

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

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

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

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

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

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

六、小结

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

AppClassLoader 委托 ExtClassLoader 加载,

ExtClassLoader 又委托 BootstrapClassLoader 加载,

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

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

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

OVER

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值