SystemClassLoader 和 AppClassLoader/ApplicationClassLoader 到底是不是同一个东西?

在很多 Java 类加载器相关文章里,我们经常会看到这样的图,完整作图我不CV了,反正大概意思如下:
典型的 ClassLoader 图

总而言之,大概意思可以总结成这样:

BootstrapClassLoader
    ↓
ExtensionClassLoader / PlatformClassLoader (自 Java9 已经弃用 ExtensionClassLoader 改为 PlatformClassLoader)
    ↓
ApplicationClassLoader / AppClassLoader / SystemClassLoader
    ↓
UserClassLoader

这一短文中不对 BootstrapClassLoaderExtensionClassLoader/PlatformClassLoaderUserClassLoader 做介绍,重点是针对 ApplicationClassLoader / AppClassLoader / SystemClassLoader 进行展开介绍。只所以要说,主要是很多文章其实会将这三者混着来说。甚至还有文章提到 "SystemClassLoader 就是 AppClassLoader,它们本质上是同一个东西。",如果只看相关资料外加理解错误,确实会得到这个错误的结论。

但实际上更加准确的结论来说,应该是:

AppClassLoader 是 JDK 内置的应用类加载器实现,而 SystemClassLoaderClassLoader.getSystemClassLoader() 返回的系统类加载器角色。
默认情况下,这个角色由内置 AppClassLoader 充当;但通过 -Djava.system.class.loader 可以将它替换成自定义类加载器。
替换后,SystemClassLoader 不再是 AppClassLoader,但内置 AppClassLoader 依然存在,并且会作为自定义系统类加载器的 parent。

以下验证以 JDK 25 作为例子开始介绍。

一、先说结论

在 JDK 9 以后,包括 JDK 25,常见的内置类加载器结构是:

Bootstrap ClassLoader
    ↑
PlatformClassLoader
    ↑
AppClassLoader
    ↑
UserClassLoader/UserdefinedClassLoader(不是真的存在这个类,只是直译为用户自己定义的 ClassLoader)

其中:

  • Bootstrap ClassLoader 负责加载 Java 核心类;
  • PlatformClassLoader 负责加载平台类;
  • AppClassLoader 负责加载应用 classpath、应用 module path 上的类;
  • ClassLoader.getSystemClassLoader() 默认返回内置的 AppClassLoader

所以在默认情况下,下面这个判断通常成立:ClassLoader.getSystemClassLoader().getClass()
结果通常是:jdk.internal.loader.ClassLoaders$AppClassLoader

于是很多人直接就说:SystemClassLoader 就是 AppClassLoader

但这个说法只说对了一半。

因为 SystemClassLoader 并不是一个固定类名,而是一个运行时角色。这个角色可以通过设置 VM Options 进行替换

-Djava.system.class.loader=你的类加载器全限定名

二、JDK 源码里的关键逻辑

事实上如果真的认真看过源码的话,那么就可以发现在 JDK 25 的 ClassLoader 源码的注释和逻辑之中,非常明显看到相关的配置信息,getSystemClassLoader() 源码逻辑如下:(因为注释太长了,这里就不显示了)

    public static ClassLoader getSystemClassLoader() {
        switch (VM.initLevel()) {
            case 0:
            case 1:
            case 2:
                // the system class loader is the built-in app class loader during startup
                return getBuiltinAppClassLoader();
            case 3:
                String msg = "getSystemClassLoader cannot be called during the system class loader instantiation";
                throw new IllegalStateException(msg);
            default:
                // system fully initialized
                assert VM.isBooted() && scl != null;
                return scl;
        }
    }

其中:

static ClassLoader getBuiltinAppClassLoader() {
    return ClassLoaders.appClassLoader();
}

也就是说,JDK 内部有一个内置的 app class loader。

真正初始化系统类加载器的源码实际内容如下:

    /*
     * Initialize the system class loader that may be a custom class on the
     * application class path or application module path.
     * @see java.lang.System#initPhase3
     */
    static synchronized ClassLoader initSystemClassLoader() {
        if (VM.initLevel() != 3) {
            throw new InternalError("system class loader cannot be set at initLevel " +
                                    VM.initLevel());
        }

        // detect recursive initialization
        if (scl != null) {
            throw new IllegalStateException("recursive invocation");
        }

        ClassLoader builtinLoader = getBuiltinAppClassLoader();
        String cn = System.getProperty("java.system.class.loader");
        if (cn != null) {
            try {
                // custom class loader is only supported to be loaded from unnamed module
                Constructor<?> ctor = Class.forName(cn, false, builtinLoader)
                                           .getDeclaredConstructor(ClassLoader.class);
                scl = (ClassLoader) ctor.newInstance(builtinLoader);
            } catch (Exception e) {
                Throwable cause = e;
                if (e instanceof InvocationTargetException) {
                    cause = e.getCause();
                    if (cause instanceof Error) {
                        throw (Error) cause;
                    }
                }
                if (cause instanceof RuntimeException) {
                    throw (RuntimeException) cause;
                }
                throw new Error(cause.getMessage(), cause);
            }
        } else {
            scl = builtinLoader;
        }
        return scl;
    }

这一段源码的意思非常明显了,无非只有两个情况出现。

情况1:没有配置 java.system.class.loader

那么 scl 必然是 builtinLoader,也就是执行结果中 SystemClassLoader 就只可能是 AppClassLoader,也就和上文的结论中提到的 "常见的内置类加载器结构"是一样的。

情况2:配置了 java.system.class.loader

我们这里假设配置的值如下:

-Djava.system.class.loader=com.leticiafeng.MySystemClassLoader

那么 JDK 会:

  1. 先获取内置的 AppClassLoader
  2. 用内置 AppClassLoader 加载我们的 MySystemClassLoader 类;
  3. 调用它的构造方法;
  4. 把内置 AppClassLoader 作为 parent 传进去;
  5. 最后把创建出来的自定义类加载器设置为 system class loader。

也就是说,实际上加载的类是:

ClassLoader builtinLoader = getBuiltinAppClassLoader();
Class<?> clazz = Class.forName(
        "com.leticiafeng.MySystemClassLoader",
        false,
        builtinLoader
);
scl = new MySystemClassLoader(builtinLoader);

所以替换之后,整个双亲委派模型就会变成:

Bootstrap ClassLoader
    ↑
PlatformClassLoader
    ↑
AppClassLoader
    ↑
MySystemClassLoader (本质上其实还是 UserClassLoader,但是它会因为 vm options 被选中为 System Class Loader)
    ↑
UserClassLoader/UserDefinitionClassLoader(不是真的存在这个类,只是直译为用户自己定义的 ClassLoader)

其实这里 UserClassLoader 并不一定是挂载在 MySystemClassLoader 上的,重写 loadClass 也可以挂载 PlatformClassLoader 头上,当然这就是打破双亲委派模型的操作了,还需要注意的一点是

三、测试代码

下面用两个类来验证这个结论。

1. Probe

package com.leticiafeng;

import java.util.Arrays;

public class Probe {
    public static void main(String[] args) {
        System.out.println("java.system.class.loader = "
                + System.getProperty("java.system.class.loader"));
        System.out.println("args = " + Arrays.toString(args));
        System.out.println();

        print("ClassLoader.getSystemClassLoader()",
                ClassLoader.getSystemClassLoader());
        print("ClassLoader.getPlatformClassLoader()",
                ClassLoader.getPlatformClassLoader());
        print("Probe.class.getClassLoader()",
                Probe.class.getClassLoader());
        print("Thread.currentThread().getContextClassLoader()",
                Thread.currentThread().getContextClassLoader());

        System.out.println();

        ClassLoader scl = ClassLoader.getSystemClassLoader();
        print("system.parent", scl.getParent());

        if (scl.getParent() != null) {
            print("system.parent.parent", scl.getParent().getParent());
        }
    }

    static void print(String label, ClassLoader cl) {
        System.out.printf("%-50s = %s%n", label, desc(cl));
    }

    static String desc(ClassLoader cl) {
        if (cl == null) {
            return "null, means BootstrapClassLoader";
        }

        return cl.getClass().getName()
                + "@"
                + Integer.toHexString(System.identityHashCode(cl))
                + ", name="
                + cl.getName();
    }
}

2. MySystemClassLoader

package com.leticiafeng;

public class MySystemClassLoader extends ClassLoader {
	// 该构造器必须存在,JDK初始化自定义 system class loader 的时候,会查找这个构造器
    // 如果它不存在,那么结果会直接报错
    public MySystemClassLoader(ClassLoader parent) {
        super("my-system-class-loader", parent);

        System.out.println("[MySystemClassLoader.<init>]");
        print("constructor parent", parent);
        print("MySystemClassLoader.class.getClassLoader()",
                MySystemClassLoader.class.getClassLoader());
        System.out.println();
    }

    static void print(String label, ClassLoader cl) {
        System.out.printf("%-50s = %s%n", label, desc(cl));
    }

    static String desc(ClassLoader cl) {
        if (cl == null) {
            return "null, means BootstrapClassLoader";
        }

        return cl.getClass().getName()
                + "@"
                + Integer.toHexString(System.identityHashCode(cl))
                + ", name="
                + cl.getName();
    }
}

四、实验

一:不指定 VM Options

为了测试验证简便,我不建议用IDE执行,最好还是采用 command line 来做这件事。我们可以直接执行

java -classpath target/classes com.leticiafeng.Probe
/* 实际输出如下 */
java.system.class.loader = null
args = []

ClassLoader.getSystemClassLoader()                 = jdk.internal.loader.ClassLoaders$AppClassLoader@14dad5dc, name=app
ClassLoader.getPlatformClassLoader()               = jdk.internal.loader.ClassLoaders$PlatformClassLoader@30f39991, name=platform
Probe.class.getClassLoader()                       = jdk.internal.loader.ClassLoaders$AppClassLoader@14dad5dc, name=app
Thread.currentThread().getContextClassLoader()     = jdk.internal.loader.ClassLoaders$AppClassLoader@14dad5dc, name=app

system.parent                                      = jdk.internal.loader.ClassLoaders$PlatformClassLoader@30f39991, name=platform
system.parent.parent                               = null, means BootstrapClassLoader
  • java.system.class.loader = null ->
    system class loader 其实是空的,没有指定系统类加载器

  • ClassLoader.getSystemClassLoader() = jdk.internal.loader.ClassLoaders$AppClassLoader@14dad5dc, name=app ->
    JDK 加载的 SystemClassLoader 的取值就是 AppClassLoader

    也就是所谓的 SystemClassLoader = AppClassLoader ,这也是为什么很多人会说 SystemClassLoaderAppClassLoader 的根本来源。

  • ClassLoader.getPlatformClassLoader() = jdk.internal.loader.ClassLoaders$PlatformClassLoader@30f39991, name=platform ->
    PlatformClassLoader 就是 PlatformClassLoader

  • Probe.class.getClassLoader() = jdk.internal.loader.ClassLoaders$AppClassLoader@14dad5dc, name=appThread.currentThread().getContextClassLoader() = jdk.internal.loader.ClassLoaders$AppClassLoader@14dad5dc, name=app ->
    Probe 和 主线程 实际上交给了 AppClassLoader 负责加载


  • system.parent = jdk.internal.loader.ClassLoaders$PlatformClassLoader@30f39991, name=platform ->
    AppClassLoaderparent 就是 PlatformClassLoader
  • system.parent.parent = null, means BootstrapClassLoader ->
    AppClassLoaderparentparnet 就是 BootstrapClassLoader引导类加载器是 BootstrapClassLoader 这很正常。

显然我们前文提到的情况一的结论是对的

这里其实有一个明显的小疑点,BootstapClassloader 并无限定名,这是因为它的实现是在JVM的源码中实现的

二:指定 java.system.class.loader

接下来指定自定义系统类加载器,为了测试方便,还是不建议用IDE,我这里继续用 command line 来介绍:

java.exe -Djava.system.class.loader=com.leticiafeng.MySystemClassLoader -classpath com.leticiafeng.Probe
/* 输出如下 */
[0.007s][warning][cds] Archived non-system classes are disabled because the java.system.class.loader property is specified (value = "com.leticiafeng.MySystemClassLoader"). To use archived non-system classes, this property must not be set

[MySystemClassLoader.<init>]
constructor parent                                 = jdk.internal.loader.ClassLoaders$AppClassLoader@5bc2b487, name=app
MySystemClassLoader.class.getClassLoader()         = jdk.internal.loader.ClassLoaders$AppClassLoader@5bc2b487, name=app

java.system.class.loader = com.leticiafeng.MySystemClassLoader
args = []

ClassLoader.getSystemClassLoader()                 = com.leticiafeng.MySystemClassLoader@355da254, name=my-system-class-loader
ClassLoader.getPlatformClassLoader()               = jdk.internal.loader.ClassLoaders$PlatformClassLoader@4d7e1886, name=platform
Probe.class.getClassLoader()                       = jdk.internal.loader.ClassLoaders$AppClassLoader@5bc2b487, name=app
Thread.currentThread().getContextClassLoader()     = com.leticiafeng.MySystemClassLoader@355da254, name=my-system-class-loader

system.parent                                      = jdk.internal.loader.ClassLoaders$AppClassLoader@5bc2b487, name=app
system.parent.parent                               = jdk.internal.loader.ClassLoaders$PlatformClassLoader@4d7e1886, name=platform
  • MySystemClassLoader.class.getClassLoader()= jdk.internal.loader.ClassLoaders$AppClassLoader@5bc2b487, name=app
    MySystemClassLoader 这个类本身是由内置 AppClassLoader 加载的,这跟我们看到的 JDK 25 源码中的

    ClassLoader builtinLoader = getBuiltinAppClassLoader();
    Constructor<?> ctor = Class.forName(cn, false, builtinLoader)
                               .getDeclaredConstructor(ClassLoader.class);
    

    是完全匹配的


  • java.system.class.loader = com.leticiafeng.MySystemClassLoader ->
    system class loader 的指定 vm option 参数配置已经生效了

  • ClassLoader.getSystemClassLoader() = com.leticiafeng.MySystemClassLoader@355da254, name=my-system-class-loader ->
    JDK 加载的 SystemClassLoader 的取值就是 MySystemClassLoader
  • ClassLoader.getPlatformClassLoader() = jdk.internal.loader.ClassLoaders$PlatformClassLoader@30f39991, name=platform ->
    PlatformClassLoader 就是 PlatformClassLoader
  • Probe.class.getClassLoader()= jdk.internal.loader.ClassLoaders$AppClassLoader@5bc2b487, name=app ->
    Probe的类加载器是 AppClassLoader,只所以不是 MySystemClassLoader 原因很简单,因为我没有重写 loadClass 和 findClass,MySystemClassLoader 遇到直接根据双亲委派模型委托给了 parent 的 AppClassLoader ,它在 target 找到了对应类并负责了加载
  • Thread.currentThread().getContextClassLoader()= com.leticiafeng.MySystemClassLoader@355da254, name=my-system-class-loader ->
    主线程的上下文类加载器仍然被设置为了 system class loader

  • system.parent= jdk.internal.loader.ClassLoaders$AppClassLoader@5bc2b487, name=app ->
    甚至 AppClassLoader 其实还是存在的,并且它还是 SystemClassLoaderparent,很显然这一步可以看出来,SystemClassLoader 和 AppClassLoader 可以不是同一个对象。
  • system.parent.parent = jdk.internal.loader.ClassLoaders$PlatformClassLoader@4d7e1886, name=platform ->
    MySystemClassLoader 的 parent 的 parnet 就是 PlatformClassLoader

五、SystemClassLoader 和 AppClassLoader 的准确关系,ApplicationClassLoader又是什么?

AppClassLoader 是什么?

JDK 9+ 提供的内置应用类加载器的实现,限定名是 jdk.internal.loader.ClassLoaders$AppClassLoader ,实际上它负责的职责是 加载应用 classpath 或 application module path 上的类

SystemClassLoader 是什么?

SystemClassLoader 不是一个固定实现类,而是:ClassLoader.getSystemClassLoader() 返回的类加载器,从运行逻辑来看,它严格来说只是一个角色,如果指定了 -Djava.system.class.loader 那么就是所制定的值,而如果没有指定,那么默认下就是 AppClassLoader

ApplicationClassLoader 又是什么?

在 JDK 25 源码里,一般找不到一个公开的、直接叫 ApplicationClassLoader 的类。在 JDK 9+ 中,默认应用类加载器实现通常是:jdk.internal.loader.ClassLoaders$AppClassLoader 要么是误传,要么是概念名

六、总结

AppClassLoader 是 JDK 内置的应用类加载器实现;SystemClassLoaderClassLoader.getSystemClassLoader() 返回的系统类加载器角色。默认情况下,SystemClassLoader 就是内置 AppClassLoader;但通过 -Djava.system.class.loader 可以把这个角色替换成自定义类加载器,此时内置 AppClassLoader 仍然存在,并作为自定义系统类加载器的 parent。

名称 含义 默认情况 自定义 java.system.class.loader
AppClassLoader JDK 内置 app 类加载器 存在 仍然存在
SystemClassLoader ClassLoader.getSystemClassLoader() 返回的角色 等于 AppClassLoader 等于自定义类加载器
ApplicationClassLoader 应用类加载器的概念名 通常指 AppClassLoader 概念上仍指应用类加载器
PlatformClassLoader 平台类加载器 AppClassLoader 的 parent AppClassLoader 的 parent
BootstrapClassLoader 启动类加载器 Java 层通常表现为 null Java 层通常表现为 null