我们都知道,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这个类的源码:
代码语言:javascript复制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机制