SystemClassLoader 和 AppClassLoader/ApplicationClassLoader 到底是不是同一个东西?
在很多 Java 类加载器相关文章里,我们经常会看到这样的图,完整作图我不CV了,反正大概意思如下:

总而言之,大概意思可以总结成这样:
BootstrapClassLoader
↓
ExtensionClassLoader / PlatformClassLoader (自 Java9 已经弃用 ExtensionClassLoader 改为 PlatformClassLoader)
↓
ApplicationClassLoader / AppClassLoader / SystemClassLoader
↓
UserClassLoader
这一短文中不对 BootstrapClassLoader、ExtensionClassLoader/PlatformClassLoader、UserClassLoader 做介绍,重点是针对 ApplicationClassLoader / AppClassLoader / SystemClassLoader 进行展开介绍。只所以要说,主要是很多文章其实会将这三者混着来说。甚至还有文章提到 "SystemClassLoader 就是 AppClassLoader,它们本质上是同一个东西。",如果只看相关资料外加理解错误,确实会得到这个错误的结论。
但实际上更加准确的结论来说,应该是:
AppClassLoader是 JDK 内置的应用类加载器实现,而SystemClassLoader是ClassLoader.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 会:
- 先获取内置的
AppClassLoader; - 用内置
AppClassLoader加载我们的MySystemClassLoader类; - 调用它的构造方法;
- 把内置
AppClassLoader作为 parent 传进去; - 最后把创建出来的自定义类加载器设置为 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,这也是为什么很多人会说SystemClassLoader和AppClassLoader的根本来源。 -
ClassLoader.getPlatformClassLoader() = jdk.internal.loader.ClassLoaders$PlatformClassLoader@30f39991, name=platform->
PlatformClassLoader就是PlatformClassLoader -
Probe.class.getClassLoader() = jdk.internal.loader.ClassLoaders$AppClassLoader@14dad5dc, name=app和Thread.currentThread().getContextClassLoader() = jdk.internal.loader.ClassLoaders$AppClassLoader@14dad5dc, name=app->
Probe 和 主线程 实际上交给了AppClassLoader负责加载
system.parent = jdk.internal.loader.ClassLoaders$PlatformClassLoader@30f39991, name=platform->
AppClassLoader的parent就是PlatformClassLoader;system.parent.parent = null, means BootstrapClassLoader->
AppClassLoader的parent的parnet就是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的取值就是MySystemClassLoaderClassLoader.getPlatformClassLoader() = jdk.internal.loader.ClassLoaders$PlatformClassLoader@30f39991, name=platform->
PlatformClassLoader就是PlatformClassLoaderProbe.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其实还是存在的,并且它还是SystemClassLoader的parent,很显然这一步可以看出来,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 内置的应用类加载器实现;SystemClassLoader 是 ClassLoader.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 |