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 ->
    ``MySystemClassLoaderparentparnet就是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
AppClassLoaderJDK 内置 app 类加载器存在仍然存在
SystemClassLoaderClassLoader.getSystemClassLoader() 返回的角色等于 AppClassLoader等于自定义类加载器
ApplicationClassLoader应用类加载器的概念名通常指 AppClassLoader概念上仍指应用类加载器
PlatformClassLoader平台类加载器AppClassLoader 的 parentAppClassLoader 的 parent
BootstrapClassLoader启动类加载器Java 层通常表现为 nullJava 层通常表现为 null