《深入理解Java虚拟机》读书笔记 类加载器双亲委派模型

从Java虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。

Java自带了以下三个ClassLoader:

  • 启动类加载器(Bootstrap ClassLoader)
    这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。
  • 扩展类加载器(Extension ClassLoader):
    这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
  • 应用程序类加载器(Application ClassLoader):
    这个类加载器由sun.misc.Launcher$App-ClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

如有必要,我们还可以实现自己的类加载器。这些类加载器的关系一般如下图所示




User ClassLoader Application ClassLoader User ClassLoader Extension ClassLoader Bootstrap ClassLoader

接下来就让我们验证下吧。我随便写了个加载器,其结构是否如上图所示呢?

public class MyClassLoader extends ClassLoader{
    public static void main(String[] args) {
        MyClassLoader classLoader = new MyClassLoader();
        System.out.println(classLoader.getParent());
        System.out.println(classLoader.getParent().getParent());
        System.out.println(classLoader.getParent().getParent().getParent());
    }
}

其输出如下:

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@133314b
null

可以看到前面两行输出确实是AppClassLoader和ExtClassLoader,可后面怎么成了null呢?接下来就从ClassLoader的源码中一探究竟吧(限于篇幅,只展示部分比较核心的代码)。
首先来看下loadClass(),这里是双亲委派模型的核心。

//定义父加载器
private final ClassLoader parent;

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先检查是否已经被自身加载 已经加载过不会重新加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                	//父加载器不为空 检查是否被父加载器加载过
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                    	//没有父加载器 检查是否被Bootstrap ClassLoader加载过
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // 父加载器与Bootstrap ClassLoader都没有加载过 开始执行加载逻辑
                    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;
        }
    }

这样做有什么好处呢?

  • 首先避免了重复加载可能导致的性能问题。
  • 保证了Java基础类库不会被覆盖,具有一定的安全性。

那上下级关系又是如何确定的呢?
接着看源码,首先看下ClassLoader的几个构造方法。

//无参构造 默认使用SystemClassLoader
protected ClassLoader() {
	this(checkCreateClassLoader(), getSystemClassLoader());
}

//传入parent的构造
protected ClassLoader(ClassLoader parent) {
	this(checkCreateClassLoader(), parent);
}

//实际逻辑
private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            package2certs = new ConcurrentHashMap<>();
            domains =
                Collections.synchronizedSet(new HashSet<ProtectionDomain>());
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            domains = new HashSet<>();
            assertionLock = this;
        }
    }

在上面的验证中我们调用了无参构造方法,可以看到其调用了getSystemClassLoader()用于返回parent,其最终是调用了sun.misc.Launcher.getLauncher().getClassLoader(),此方法源码如下:

public ClassLoader getClassLoader() {
	return this.loader;
}

这个loader又是哪里来的呢?好在Launcher只有一个构造方法。

public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
        	//初始化 ExtClassLoader
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
        	//使用ExtClassLoader作为父加载器创建AppClassLoader
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if (var2 != null) {
            SecurityManager var3 = null;
            if (!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                } catch (InstantiationException var6) {
                } catch (ClassNotFoundException var7) {
                } catch (ClassCastException var8) {
                }
            } else {
                var3 = new SecurityManager();
            }

            if (var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }

            System.setSecurityManager(var3);
        }

}

从代码中可以清楚的了解到其首先创建了ExtClassLoader,随后使用ExtClassLoader作为父加载器创建了AppClassLoader,进而保证了其上下级顺序。

    原文作者:java虚拟机
    原文地址: https://blog.csdn.net/chenpengjia006/article/details/85625554
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞