前言:网上关于类加载器讲解的文章特别多,在学习的时候让我受益匪浅,某段时间觉得自己懂了。但是在昨天遇到一个问题,并去看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)加载器加载,依次递归,如果父类加载器可以完成类加载任务,就成功返回,只有当父类加载器无法完成此加载任务时,才自己去加载
双亲委派机制下三种类加载器的关系(图片来自网络):

ClassLoaderA和ClassLoaderB是我们自己实现的类加载器,这里都指定了其父加载器为APPClassLoader,当然你也可以指定ClassLoaderA的父加载器为ClassLoaderB,但由于往上最多只能拿到SystemClassLoader的引用,所以父加载器最多只能指定到SystemClassLoader
通过 java.lang.ClassLoader.getSystemClassLoader
我们可以获取到JVM启动时加载ClassPath里jar包的应用类加载器SystemClassLoader,由此我们可以从代码上看到上面的关系图:

这里可以看到扩展类加载器的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
方法调用了 native
的 findBootstrapClass
方法,所以是映射到了Bootstrap类加载器的
再来看看扩展类加载器和应用类加载器的类图

可以发现它们都继承自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