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

 2019-12-22 11:22  阅读(988)
文章分类:JVM

从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,进而保证了其上下级顺序。

点赞(1)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> 《深入理解Java虚拟机》读书笔记 类加载器双亲委派模型

相关推荐