基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

Java 中常见的类加载器(Bootstrap、Extension、Application/System ClassLoader)及其负责加载的路径

知识点图片

在 Java 中,类加载器(ClassLoader)负责将编译好的 .class 字节码文件加载到 JVM 内存中。Java 默认提供了三个核心的类加载器,它们形成了一个层级结构。

以下是这三种常见类加载器的详细介绍及其负责加载的路径(注:以下路径描述主要基于 Java 8 及更早版本,Java 9 引入模块化后有一定改变,文末会有补充说明)。


1. Bootstrap ClassLoader(启动类加载器)

这是最顶层的类加载器,它是 JVM 自身的一部分。

  • 实现语言:C/C++(在 HotSpot 虚拟机中)。由于它不是 Java 编写的,所以在 Java 代码中获取它会返回 null(例如 String.class.getClassLoader() == null)。
  • 职责:负责加载 Java 的核心类库(即 Java 运行最基础的类,如 java.lang.*java.util.* 等)。
  • 加载路径
    • <JAVA_HOME>/jre/lib 目录下的核心 jar 包,最典型的是 rt.jarresources.jarcharsets.jar 等。
  • 对应的系统属性:可通过 System.getProperty("sun.boot.class.path") 查看其加载的具体路径。

2. Extension ClassLoader(扩展类加载器)

它是启动类加载器的下级(在逻辑上,而非继承关系)。

  • 实现语言:Java(具体实现类为 sun.misc.Launcher$ExtClassLoader)。
  • 职责:负责加载 Java 的扩展类库。Sun/Oracle 预留了这个目录,允许开发者将一些需要全局使用的第三方或扩展 jar 包放在这里,JVM 会自动加载。
  • 加载路径
    • <JAVA_HOME>/jre/lib/ext 目录。
    • 或者由系统变量 -Djava.ext.dirs 指定的目录中的类库。
  • 对应的系统属性:可通过 System.getProperty("java.ext.dirs") 查看。

3. Application / System ClassLoader(应用程序/系统类加载器)

它是扩展类加载器的下级,也是普通 Java 程序的默认类加载器

  • 实现语言:Java(具体实现类为 sun.misc.Launcher$AppClassLoader)。
  • 职责:负责加载开发者自己编写的类以及第三方依赖(Maven/Gradle引入的包)
  • 加载路径
    • 环境变量 CLASSPATH 指定的目录。
    • 命令行启动时通过 -cp-classpath 指定的目录和 jar 包。
  • 获取方式:可以通过 ClassLoader.getSystemClassLoader() 方法直接获取。
  • 对应的系统属性:可通过 System.getProperty("java.class.path") 查看。

💡 核心机制:双亲委派模型 (Parent Delegation Model)

这三个类加载器协同工作的方式被称为双亲委派模型。它的工作流程如下:

  1. 当 AppClassLoader 收到一个加载类的请求时,它首先不会自己去加载,而是把请求委派给父加载器(ExtClassLoader)。
  2. ExtClassLoader 收到请求后,同样不会自己先加载,而是继续委派给它的父加载器(Bootstrap ClassLoader)。
  3. Bootstrap ClassLoader 尝试在其负责的路径(jre/lib)寻找。如果找到,就加载并返回;如果找不到,就将请求退回给子加载器
  4. ExtClassLoader 尝试在 jre/lib/ext 寻找。找到就加载,找不到就退回给 AppClassLoader
  5. AppClassLoader 尝试在 CLASSPATH 寻找。如果依然找不到,就会抛出 ClassNotFoundException

为什么这样做?(核心优点)

  • 沙箱安全机制:防止核心 API 被篡改。例如,如果你自己写了一个 java.lang.String 类,由于双亲委派,请求会一直送到顶层,Bootstrap 会加载官方核心库中的 String 类,而你自己写的 String 类永远不会被加载。
  • 避免类的重复加载:父类加载器已经加载过的类,子加载器不需要再加载一次。

⚠️ 补充:Java 9 及之后的改变 (模块化系统 JPMS)

从 Java 9 开始,引入了模块化系统,类加载架构发生了一些调整:

  1. 没有了 rt.jarext 目录:JDK 核心类库被拆分成了多个 JMOD 文件(放在 <JAVA_HOME>/jmods 目录下)。
  2. Extension ClassLoader 被重命名为 Platform ClassLoader(平台类加载器)
    • 因为 ext 目录被取消了,扩展机制被移除。
    • Platform ClassLoader 负责加载 Java SE 和 JDK 的部分模块。
  3. Bootstrap ClassLoader 依然存在,但现在它在 JVM 内部负责加载基础的核心模块(如 java.basejava.logging 等)。
  4. 双亲委派机制依然保留,但增加了一个模块寻址的步骤:在委派给父加载器之前,会先判断该类属于哪个具体的模块,如果属于某个已知的模块,就会直接把请求交给负责该模块的类加载器。

验证代码(在 Java 8 下运行)

你可以运行以下代码直观地感受它们:

java
public class ClassLoaderTest {
    public static void main(String[] args) {
        // 1. 获取自定义类的加载器 (AppClassLoader)
        ClassLoader appClassLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println("当前类的加载器: " + appClassLoader);

        // 2. 获取 AppClassLoader 的父加载器 (ExtClassLoader)
        ClassLoader extClassLoader = appClassLoader.getParent();
        System.out.println("父加载器: " + extClassLoader);

        // 3. 获取 ExtClassLoader 的父加载器 (Bootstrap ClassLoader)
        ClassLoader bootClassLoader = extClassLoader.getParent();
        System.out.println("祖父加载器: " + bootClassLoader); // 输出 null,因为是 C++ 实现的

        // 4. 获取核心类 String 的加载器
        ClassLoader stringClassLoader = String.class.getClassLoader();
        System.out.println("String类的加载器: " + stringClassLoader); // 输出 null,由 Bootstrap 加载
    }
}
00:00
00:00