zl程序教程

您现在的位置是:首页 >  Java

当前栏目

JAVA ClassLoader双亲委派机制细节研究

2023-03-15 22:00:48 时间

我们都知道,Java类都是靠ClassLoader来加载的,而类加载器也是java类,因而java类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这个加载器正是BootstrapClassLoader。由于它不是Java类,因此它不需要被别人加载,而嵌套在Java虚拟机内核里面,也就是JVM启动的时候Bootstrap就已经启动,它是用C++写的二进制代码(不是字节码),它可以去加载别的类。

我们还知道,JAVA虚拟机的ClassLoader采用“双亲委派”机制,如下图所示:

图中BootstrapClassLoader、ExtClassLoader 和 AppClassLoader是JVM中内置了三个重要的 ClassLoader,其中ExtClassLoader和AppClassLoader都是Launcher的静态内部类:

BootstrapClassLoader 负责加载 JVM 运行时核心类,这些类位于 JAVA_HOME/lib/rt.jar 文件中,我们常用内置库 java.xxx.* 都在里面,比如 java.util.*、java.io.*、java.nio.*、java.lang.* 等等。这个 ClassLoader 比较特殊,它是由 C 代码实现的,我们将它称之为“根加载器”。

ExtClassLoader 负责加载 JVM 扩展类,比如 swing 系列、内置的 js 引擎、xml 解析器 等等,这些库名通常以 javax 开头,它们的 jar 包位于 JAVA_HOME/lib/ext/*.jar 中,有很多 jar 包。

AppClassLoader 才是直接面向我们用户的加载器,它会加载 Classpath 环境变量里定义的路径中的 jar 包和目录。我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载的。

所谓“双亲委派”就是指当前类加载器在决定加载一个类之前,不是直接自己加载,而是先委派其父类来加载,如果其父类能加载该类,则直接返回父类加载的类,否则才会自行加载。

此外,参考博客老大难的 Java ClassLoader,到了该彻底理解它的时候了中还有如下一段描述:

图中的 ExtensionClassLoader 的 parent 指针画了虚线,这是因为它的 parent 的值是 null,当 parent 字段是 null 时就表示它的父加载器是「根加载器」。如果某个 Class 对象的 classLoader 属性值是 null,那么就表示这个类也是“根加载器”加载的。

看到这段话我不禁疑惑,既然ExtensionClassLoader的parent为null,那么ExtClassLoader是如何委派给BootstrapClassLoader呢?直到我看了ClassLoader.java这个类的源码:

public abstract class ClassLoader {
    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 {
                        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;
        }
    }
    
    private Class<?> findBootstrapClassOrNull(String name)
    {
        if (!checkName(name)) return null;
    
        return findBootstrapClass(name);
    }
    
    // return null if not found
    private native Class<?> findBootstrapClass(String name);
    }

由源码可知,在ClassLoader的loadClass方法中,当前的ClassLoader的parent如果为null,则会调用其自定义的findBootstrapClassOrNull方法,而后者最终调用了findBootstrapClass方法。该方法为native方法,由该方法名可以知道,该方法就是用于查找BootstrapClassLoader加载的Class。

至于为什么要将ExtClassLoader的parent设计为null,由前面的介绍就很容易理解了:BootstrapClassLoader不是Java类,而是直接嵌套在Java虚拟机内核里面,因而ExtClassLoader无法引用BootstrapClassLoader。

最后补充一下类加载器的命名空间:每个类加载器对应一个命名空间,命名空间起到了一个类相互隔离的作用。而关于不同类加载器对应的命名空间中的类之间的可见性如下(参考):

  • 同一个命名空间内的类是相互可以见的
  • 子加载器的命名空间包含所有父加载器的命名空间。因此由子加载器加载的类能看见父加载器加载的类。
  • 由父加载器加载的类不能看见子加载器加载的类。
  • 如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类相互不可见

最后,需要强调的是,双亲委派模型也并非是完美无缺,一些特殊的场景下,是没有遵守双亲委派机制的,详见参考博客9~11。

参考博客:

1、http://blog.itpub.net/31561269/viewspace-2222522/ 老大难的 Java ClassLoader,到了该彻底理解它的时候了

2、https://www.jianshu.com/p/2000f9d805ef Java类的加载和初始化

3、https://blog.csdn.net/daochuwenziyao/article/details/77689154 类加载器简述

4、https://www.ibm.com/developerworks/cn/java/j-lo-classloader/ 深入探讨 Java 类加载器

5、https://www.cnblogs.com/lanxuezaipiao/p/4138511.html 关于java类加载双亲委派机制的思考

6、https://blog.csdn.net/weixin_43258908/article/details/89094291 Java类的加载方式、类的初始化、类的执行方式

7、https://www.jianshu.com/p/2840e87de2b7 Java类加载器

8、https://www.jianshu.com/p/e74fe532e35e JVM知识整理

9、https://www.jianshu.com/p/09f73af48a98  以JDBC为例谈双亲委派模型的破坏

10、JDBC、Tomcat为什么要破坏双亲委派模型? 重要

11、https://www.jianshu.com/p/60dbd8009c64 聊聊JDBC是如何破坏双亲委派模型的   重要

12、https://www.jianshu.com/p/3a3edbcd8f24  深入理解SPI机制