引言

为什么要学习这个主题?

在上一篇文章中,我们了解了类加载的完整生命周期。但你是否想过:

  • JVM中有多少个类加载器?
  • 为什么要设计多个类加载器?
  • java.lang.String和我们自己写的类是由同一个加载器加载的吗?

理解类加载器的层次结构,就像理解一个组织的部门分工。不同的类加载器负责加载不同来源的类,这种设计既保证了安全性,又提供了灵活性。

你将学到什么?

  • ✅ JVM的三层类加载器体系
  • ✅ 每个类加载器的职责和加载路径
  • ✅ 类加载器的父子关系(委派关系)
  • ✅ 如何查看类是由哪个加载器加载的
  • ✅ 线程上下文类加载器

一、类加载器的层次结构

1.1 三层体系

┌────────────────────────────────────────┐
│   启动类加载器                          │  Bootstrap ClassLoader
│   (Bootstrap ClassLoader)              │  C++实现,加载核心类库
│   加载:rt.jar、核心API                │
└──────────────┬─────────────────────────┘
               │ 父加载器
┌──────────────▼─────────────────────────┐
│   扩展类加载器                          │  Extension ClassLoader
│   (Extension ClassLoader)              │  Java实现,加载扩展库
│   加载:lib/ext/*.jar                  │
└──────────────┬─────────────────────────┘
               │ 父加载器
┌──────────────▼─────────────────────────┐
│   应用类加载器                          │  Application ClassLoader
│   (Application ClassLoader)            │  Java实现,加载应用类
│   加载:classpath下的类                │
└────────────────────────────────────────┘
               │ 父加载器(可自定义)
┌──────────────▼─────────────────────────┐
│   自定义类加载器                        │  User ClassLoader
│   (Custom ClassLoader)                 │  继承ClassLoader
│   加载:自定义路径的类                  │
└────────────────────────────────────────┘

关键理解

  • 这是一种委派关系,不是继承关系
  • 父加载器在上层,子加载器在下层
  • 加载类时,先委派给父加载器尝试

二、启动类加载器(Bootstrap ClassLoader)

2.1 特点

最顶层的类加载器,负责加载JVM核心类库。

特殊之处

  • 由C++实现(不是Java类)
  • 在Java代码中无法直接引用
  • 返回值为null

2.2 加载路径

加载的类库

<JAVA_HOME>/jre/lib/rt.jar
<JAVA_HOME>/jre/lib/resources.jar
<JAVA_HOME>/jre/lib/charsets.jar
<JAVA_HOME>/jre/lib/jce.jar
...以及其他核心类库

包含的包

java.lang.*      // 核心类(String、Object、System等)
java.util.*      // 工具类(List、Map等)
java.io.*        // IO类
java.nio.*       // NIO类
java.net.*       // 网络类
java.security.*  // 安全类
...

2.3 示例:验证启动类加载器

public class BootstrapDemo {
    public static void main(String[] args) {
        // 获取String类的类加载器
        ClassLoader loader = String.class.getClassLoader();
        System.out.println("String类的类加载器: " + loader);

        // 获取Object类的类加载器
        ClassLoader loader2 = Object.class.getClassLoader();
        System.out.println("Object类的类加载器: " + loader2);
    }
}

输出

String类的类加载器: null
Object类的类加载器: null

说明:返回null表示由启动类加载器加载(C++实现,无法获取引用)。

2.4 查看启动类加载器的搜索路径

public class BootstrapPathDemo {
    public static void main(String[] args) {
        // 获取启动类加载器的搜索路径
        String paths = System.getProperty("sun.boot.class.path");
        String[] pathArray = paths.split(":");

        System.out.println("启动类加载器的搜索路径:");
        for (String path : pathArray) {
            System.out.println(path);
        }
    }
}

输出示例(macOS):

/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home/lib/modules
/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home/lib/jrt-fs.jar

三、扩展类加载器(Extension ClassLoader)

3.1 特点

加载JDK的扩展库,提供额外功能。

实现类

Java 8:sun.misc.Launcher$ExtClassLoader
Java 9+:jdk.internal.loader.ClassLoaders$PlatformClassLoader

3.2 加载路径

加载的类库

<JAVA_HOME>/jre/lib/ext/*.jar
或系统属性 java.ext.dirs 指定的目录

包含的包

javax.*          // Java扩展包
sun.*            // Sun专有实现
com.sun.*        // Sun公司的工具类
...

3.3 示例:验证扩展类加载器

public class ExtensionDemo {
    public static void main(String[] args) {
        // 获取扩展类加载器
        ClassLoader extLoader = ClassLoader.getSystemClassLoader().getParent();
        System.out.println("扩展类加载器: " + extLoader);

        // 查看扩展类加载器的搜索路径
        String paths = System.getProperty("java.ext.dirs");
        System.out.println("扩展类加载器搜索路径:");
        if (paths != null) {
            for (String path : paths.split(":")) {
                System.out.println(path);
            }
        } else {
            System.out.println("Java 9+ 已移除 java.ext.dirs");
        }
    }
}

输出(Java 8):

扩展类加载器: sun.misc.Launcher$ExtClassLoader@xxxxx
扩展类加载器搜索路径:
/Library/Java/Extensions
/System/Library/Java/Extensions

Java 9+ 的变化

  • 扩展类加载器改名为平台类加载器(Platform ClassLoader)
  • 不再使用lib/ext目录
  • 改用模块化系统

四、应用类加载器(Application ClassLoader)

4.1 特点

加载应用程序的类,也称为系统类加载器。

实现类

Java 8:sun.misc.Launcher$AppClassLoader
Java 9+:jdk.internal.loader.ClassLoaders$AppClassLoader

4.2 加载路径

加载的类

classpath下的所有类
-cp 或 -classpath 指定的路径
系统属性 java.class.path 指定的路径

包括

  • 用户自己编写的类
  • 第三方库(如Spring、MyBatis)
  • Maven/Gradle依赖的jar包

4.3 示例:验证应用类加载器

public class AppDemo {
    public static void main(String[] args) {
        // 获取应用类加载器
        ClassLoader appLoader = ClassLoader.getSystemClassLoader();
        System.out.println("应用类加载器: " + appLoader);

        // 获取当前类的类加载器
        ClassLoader myLoader = AppDemo.class.getClassLoader();
        System.out.println("AppDemo的类加载器: " + myLoader);

        // 验证是否相同
        System.out.println("是否相同: " + (appLoader == myLoader));

        // 查看classpath
        String classpath = System.getProperty("java.class.path");
        System.out.println("\nClasspath:");
        for (String path : classpath.split(":")) {
            System.out.println(path);
        }
    }
}

输出

应用类加载器: jdk.internal.loader.ClassLoaders$AppClassLoader@xxxxx
AppDemo的类加载器: jdk.internal.loader.ClassLoaders$AppClassLoader@xxxxx
是否相同: true

Classpath:
/Users/user/project/target/classes
/Users/user/.m2/repository/...

五、父子关系与委派关系

5.1 不是继承关系

重要:类加载器之间是组合关系,不是继承关系。

public abstract class ClassLoader {
    private final ClassLoader parent;  // 父加载器引用

    protected ClassLoader(ClassLoader parent) {
        this.parent = parent;
    }

    public final ClassLoader getParent() {
        return parent;
    }
}

5.2 查看父子关系

public class ParentDemo {
    public static void main(String[] args) {
        ClassLoader appLoader = ClassLoader.getSystemClassLoader();
        ClassLoader extLoader = appLoader.getParent();
        ClassLoader bootLoader = extLoader.getParent();

        System.out.println("应用类加载器: " + appLoader);
        System.out.println("扩展类加载器: " + extLoader);
        System.out.println("启动类加载器: " + bootLoader);

        // 验证层次关系
        System.out.println("\n层次关系:");
        ClassLoader current = appLoader;
        int level = 1;
        while (current != null) {
            System.out.println("Level " + level + ": " + current);
            current = current.getParent();
            level++;
        }
        System.out.println("Level " + level + ": Bootstrap ClassLoader (C++)");
    }
}

输出

应用类加载器: jdk.internal.loader.ClassLoaders$AppClassLoader@xxxxx
扩展类加载器: jdk.internal.loader.ClassLoaders$PlatformClassLoader@xxxxx
启动类加载器: null

层次关系:
Level 1: jdk.internal.loader.ClassLoaders$AppClassLoader@xxxxx
Level 2: jdk.internal.loader.ClassLoaders$PlatformClassLoader@xxxxx
Level 3: Bootstrap ClassLoader (C++)

六、实战:查看类是由哪个加载器加载的

6.1 工具方法

public class LoaderUtil {
    public static void printClassLoader(Class<?> clazz) {
        System.out.println("=== " + clazz.getName() + " ===");

        ClassLoader loader = clazz.getClassLoader();
        if (loader == null) {
            System.out.println("加载器: Bootstrap ClassLoader (C++)");
        } else {
            System.out.println("加载器: " + loader.getClass().getName());

            // 打印父加载器链
            System.out.println("父加载器链:");
            int level = 1;
            ClassLoader parent = loader;
            while (parent != null) {
                System.out.println("  Level " + level + ": " + parent.getClass().getName());
                parent = parent.getParent();
                level++;
            }
            System.out.println("  Level " + level + ": Bootstrap ClassLoader (C++)");
        }
        System.out.println();
    }
}

6.2 测试不同来源的类

public class LoaderTest {
    public static void main(String[] args) {
        // 1. JDK核心类
        LoaderUtil.printClassLoader(String.class);
        LoaderUtil.printClassLoader(Object.class);

        // 2. JDK扩展类
        LoaderUtil.printClassLoader(sun.security.ec.SunEC.class);

        // 3. 应用程序类
        LoaderUtil.printClassLoader(LoaderTest.class);

        // 4. 第三方库类(如果有)
        // LoaderUtil.printClassLoader(org.springframework.context.ApplicationContext.class);
    }
}

输出

=== java.lang.String ===
加载器: Bootstrap ClassLoader (C++)

=== java.lang.Object ===
加载器: Bootstrap ClassLoader (C++)

=== sun.security.ec.SunEC ===
加载器: jdk.internal.loader.ClassLoaders$PlatformClassLoader
父加载器链:
  Level 1: jdk.internal.loader.ClassLoaders$PlatformClassLoader
  Level 2: Bootstrap ClassLoader (C++)

=== LoaderTest ===
加载器: jdk.internal.loader.ClassLoaders$AppClassLoader
父加载器链:
  Level 1: jdk.internal.loader.ClassLoaders$AppClassLoader
  Level 2: jdk.internal.loader.ClassLoaders$PlatformClassLoader
  Level 3: Bootstrap ClassLoader (C++)

七、线程上下文类加载器

7.1 问题背景

场景:JDBC驱动加载

DriverManager(核心类,启动类加载器加载)
    ↓ 需要加载
MySQL驱动(第三方库,应用类加载器加载)

问题

  • 启动类加载器加载的类,无法加载应用类加载器的类
  • 违反了双亲委派模型的单向委派原则

7.2 解决方案

线程上下文类加载器(Thread Context ClassLoader)

// 设置线程上下文类加载器
Thread.currentThread().setContextClassLoader(classLoader);

// 获取线程上下文类加载器
ClassLoader loader = Thread.currentThread().getContextClassLoader();

7.3 JDBC示例

// DriverManager(JDK核心类)
public class DriverManager {
    static {
        // 使用线程上下文类加载器加载驱动
        loadInitialDrivers();
    }

    private static void loadInitialDrivers() {
        // 获取线程上下文类加载器
        ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(
            Driver.class,
            Thread.currentThread().getContextClassLoader()  // ← 使用上下文加载器
        );

        Iterator<Driver> driversIterator = loadedDrivers.iterator();
        while(driversIterator.hasNext()) {
            driversIterator.next();
        }
    }
}

说明

  • DriverManager由启动类加载器加载
  • 但它通过线程上下文类加载器加载MySQL驱动
  • 默认的线程上下文类加载器是应用类加载器

八、对比总结

8.1 三层类加载器对比表

特性启动类加载器扩展类加载器应用类加载器
实现语言C++JavaJava
父加载器启动类加载器扩展类加载器
加载路径<JAVA_HOME>/lib<JAVA_HOME>/lib/extclasspath
加载内容核心类库(rt.jar)扩展库应用程序类
返回值null具体对象具体对象
典型类java.lang.*javax.*用户自定义类

8.2 类加载器的职责划分

启动类加载器:
    - 加载JVM运行所需的核心类
    - 保证核心类的安全性和不可篡改性

扩展类加载器:
    - 加载JDK的扩展功能
    - 提供可选的标准库

应用类加载器:
    - 加载用户的应用程序
    - 加载第三方库

自定义类加载器:
    - 加密类的加载
    - 热部署
    - 模块隔离

总结

核心要点回顾

  1. 三层类加载器体系

    • 启动类加载器:C++实现,加载核心类库
    • 扩展类加载器:Java实现,加载扩展库
    • 应用类加载器:Java实现,加载应用程序
  2. 父子关系

    • 是组合关系,不是继承关系
    • 通过parent字段引用父加载器
    • 启动类加载器返回null
  3. 职责划分

    • 不同的加载器加载不同来源的类
    • 保证核心类的安全性
    • 提供扩展和自定义能力
  4. 线程上下文类加载器

    • 解决父加载器需要加载子加载器类的问题
    • JDBC、SPI等场景使用

思考题

  1. 为什么启动类加载器返回null?这样设计有什么好处?
  2. 为什么要设计三层类加载器,而不是一个加载器加载所有类?
  3. 线程上下文类加载器破坏了双亲委派模型吗?

下一篇预告

下一篇《双亲委派模型:为什么要这样设计?》,我们将深入探讨:

  • 双亲委派模型的完整流程
  • 为什么要设计双亲委派模型
  • 双亲委派带来的好处和限制
  • 如何验证双亲委派模型的工作

参考资料


欢迎在评论区分享你对类加载器的理解!