ClassLoader和双亲委派机制


声明:本文转载自https://my.oschina.net/kavn/blog/1579576,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

前言:网上关于类加载器讲解的文章特别多,在学习的时候让我受益匪浅,某段时间觉得自己懂了。但是在昨天遇到一个问题,并去看Spark关于用户类加载的时候,它实现的类加载器让我看的很疑惑,半天没有转过来。我才发现自己原来根本不懂类加载器的原理,对双亲委派机制只是停留在字面上,知道会先找父但是不知道怎么去找的,所以今天把JDK关于ClassLoader的代码撸了一遍,把以前一些模糊的地方捋明白了,内心稍安。同时这也是我昨天遇到的问题的前篇,扫清后面问题的障碍,后续会把关于Spark的问题捋出来,再来分享

三种缺省类加载器

当一个JVM启动的时候,Java默认有三种类加载器

  • 启动(Bootstrap)类加载器:Bootstrap类加载器是由C和C++实现的类加载器,它负责将 <Java_Runtime_Home>/lib 或者由 -Xbootclasspath 指定位置下的类库加载到内存中。由于是native的,所以我们无法直接接触它,也无法直接引用它。在JDK的ClassLoader类里可以看到关于它的方法调用都是private native
  • 扩展(Exttension)类加载器:ExtClassLoader是由Sun公司提供的实现,它负责将 < Java_Runtime_Home >/lib/ext 或者由系统变量 java.ext.dir 指定位置中的类库加载到内存中,在 sun.misc.Launcher$ExtClassLoader 可看到源码,由于它的访问权限是default,所以只能是包内可见,我们外部无法直接引用它
  • 应用(Application)类加载器(也称系统类加载器SystemClassLoader):AppClassLoader也是由Sun公司提供的实现,它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中,在 sun.misc.Launcher$AppClassLoader 可看到源码,它的访问权限同样是default,所以只能是包内可见,我们外部无法直接引用它

双亲委派机制

通俗的讲,就是某个特定的类加载器在接到加载类的请求时,它首先会查看自己已加载的类中是否有这个类,如果有就返回,如果没有就将加载任务委托给父类(parent)加载器加载,依次递归,如果父类加载器可以完成类加载任务,就成功返回,只有当父类加载器无法完成此加载任务时,才自己去加载

双亲委派机制下三种类加载器的关系(图片来自网络):

classloader relation

ClassLoaderA和ClassLoaderB是我们自己实现的类加载器,这里都指定了其父加载器为APPClassLoader,当然你也可以指定ClassLoaderA的父加载器为ClassLoaderB,但由于往上最多只能拿到SystemClassLoader的引用,所以父加载器最多只能指定到SystemClassLoader

通过 java.lang.ClassLoader.getSystemClassLoader 我们可以获取到JVM启动时加载ClassPath里jar包的应用类加载器SystemClassLoader,由此我们可以从代码上看到上面的关系图:

classloader

这里可以看到扩展类加载器的parent为null,并不是Bootstrap类加载器,那双亲委派到这一级是如何实现的呢? 其次应用类加载器的父加载器为什么是扩展类加载器呢?

双亲委派机制的实现

关系图里父加载器的来源

扩展类加载器和应用类加载器都是由JVM创建的,通过 sun.misc.Launcher 类的构造函数我们可以看到创建过程(具体看注释),如下:

public Launcher() {   Launcher.ExtClassLoader var1;   try {     // 先创建扩展类加载器,加载<Java_Runtime_Home>/lib/ext下的jar包,扩展类加载器的构造方法默认指定的父类加载器就是null,因为我们引用不到BootstrapClassLoader     var1 = Launcher.ExtClassLoader.getExtClassLoader();   } catch (IOException var10) {     throw new InternalError("Could not create extension class loader");   }    try {     // 接着创建应用类加载器,并指定其父加载器为上面创建的ExtClassLoader,由此其父加载器为ExtClassLoader的关系成立     this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);   } catch (IOException var9) {     throw new InternalError("Could not create application class loader");   }   // 并把当前现场的类加载器设置为应用类加载器   Thread.currentThread().setContextClassLoader(this.loader);      // ... other code  } 

由于Launcher类里有自己的静态实例,所以该类被BootstrapClassLoader加载的时候就执行了上述过程,创建了扩展类加载器和应用类加载器,并且指定了父加载器关系

双亲委派的机制

除启动类加载器是native实现的外,其他所有类加载器都是继承自 java.lang.ClassLoader 抽象类,该类有一个protected的loadClass方法,双亲委派机制就在该方法中

protected Class<?> loadClass(String name, boolean resolve)     throws ClassNotFoundException {     synchronized (getClassLoadingLock(name)) {         // First, check if the class has already been loaded         Class c = findLoadedClass(name);         if (c == null) {             long t0 = System.nanoTime();             try {                 // 检查父加载器                 if (parent != null) {                     c = parent.loadClass(name, false);                 } else {                     // 使用Bootstrap类加载器加载,所以当ExtClassLoader的parent为null时,它会请求Bootstrap类加载器加载,这样双亲委派机制就是成立的                     c = findBootstrapClassOrNull(name);                 }             } catch (ClassNotFoundException e) {                 // ClassNotFoundException thrown if class not found                 // from the non-null parent class loader             }              if (c == null) {                 // If still not found, then invoke findClass in order                 // to find the class.                 long t1 = System.nanoTime();                 // 自己加载                 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;     } } 

> 其中 findBootstrapClassOrNull 方法调用了 nativefindBootstrapClass 方法,所以是映射到了Bootstrap类加载器的

再来看看扩展类加载器和应用类加载器的类图

structure

可以发现它们都继承自URLClassLoader类,而URLClassLoader类又继承自SecureClassLoader类,在这两个父类中都没有对ClassLoader抽象类的 loadClass 方法重写,而除了AppClassLoader类对 loadClass 方法做了简单包装之外,都没有去更改ClassLoader抽象类的 loadClass 方法原始逻辑,所以 这几个类加载器都保留了双亲委派机制,而如果要改变这种机制,我们可以通过重写这个方法实现

自己实现ClassLoader

实现一个ClassLoader比较简单,我们像扩展类加载器和应用类加载器一样继承 URLClassLoader 就可以实现我们自己的类加载器,并利用它加载包和我们的class

import java.io.File; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader;  public class MyClassLoaderTest {   static class MyClassLoader extends URLClassLoader {     public MyClassLoader(URL[] urls, ClassLoader parent) {       super(urls, parent);     }      // 对外暴露addURL方法     @Override     public void addURL(URL url) {       super.addURL(url);     }   }      public static void main(String[] args) throws Exception {     String path = "D:\\maven\\repository\\commons-codec\\commons-codec\\1.9\\commons-codec-1.9.jar";      // 也可用 ClassLoader.getSystemClassLoader() 获取应用类加载器作为父加载器或者直接置父加载器为null     MyClassLoader myClassLoader = new MyClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());     myClassLoader.addURL(new File(path).toURI().toURL());      // 加载包里的类     Class<?> clazz = myClassLoader.loadClass("org.apache.commons.codec.digest.DigestUtils");     Method md5Hex = clazz.getDeclaredMethod("md5Hex", String.class);     // 执行MD5     String result = (String) md5Hex.invoke(null, "my classloader test");     // 判断结果     assert result.equals("53c6c4f7ceadadcf36ce1386678fb61b");     // 设置当前线程的类加载器为我自己的类加载器     // Thread.currentThread().setContextClassLoader(myClassLoader);   } } 

欢迎阅读转载,转载请注明出处:https://my.oschina.net/kavn/blog/1579576

本文发表于2017年11月24日 22:33
(c)注:本文转载自https://my.oschina.net/kavn/blog/1579576,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 3981 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

万稳万当,不如一默。任何一句话,你不说出来便是那句话的主人,你说了出来,便是那句话的奴隶。

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1