AppClassLoader与ExtensionClassLoader的边界由JVM启动参数和目录结构决定:前者加载-classpath路径(含BOOT-INF/classes及lib),后者加载$JAVA_HOME/jre/lib/ext或-Djava.ext.dirs指定路径;验证需结合jps -lvm、-verbose:class及类加载器打印。

微服务启动时,AppClassLoader 和 ExtensionClassLoader 的加载边界,本质上是 类路径(class path)与扩展路径(ext path)的物理分界,不是靠代码逻辑判断,而是由 JVM 启动参数和目录结构决定的。定位关键在于搞清“谁加载了哪个类”以及“为什么是它加载”。
一、明确两者的默认加载范围
ExtensionClassLoader
加载$JAVA_HOME/jre/lib/ext(或通过-Djava.ext.dirs指定)目录下的 JAR/CLASS 文件;
例如:junit-platform-launcher-1.10.0.jar若放在此目录,就会被 ExtensionClassLoader 加载。-
AppClassLoader(也叫 System ClassLoader)
加载-classpath(或-cp)或CLASSPATH环境变量指定的所有路径,包括:-
BOOT-INF/classes/(Spring Boot Fat Jar 内部) -
BOOT-INF/lib/*.jar中的非META-INF/MANIFEST.MF声明为Class-Path的依赖(实际由 AppClassLoader 加载) - 显式传入的
-cp lib/a.jar:classes/
-
✅ 注意:Spring Boot 的
LaunchedURLClassLoader是 AppClassLoader 的子类,但它不改变双亲委派链——仍把请求先交给 ExtensionClassLoader,再往上到 Bootstrap。
二、运行时快速定位加载器归属
用以下任意一种方式,在微服务启动初期(如 @PostConstruct 或 SpringApplicationRunListener)打印类的加载器:
System.out.println("String loaded by: " + String.class.getClassLoader()); // null → Bootstrap
System.out.println("JunitPlatformRunner loaded by: " +
org.junit.platform.launcher.core.LauncherFactory.class.getClassLoader());
System.out.println("YourAppConfig loaded by: " +
com.example.MyAppConfig.class.getClassLoader());- 输出为
null:Bootstrap 加载(如java.lang.*) - 输出形如
sun.misc.Launcher$ExtClassLoader@...:ExtensionClassLoader - 输出形如
sun.misc.Launcher$AppClassLoader@...或org.springframework.boot.loader.LaunchedURLClassLoader@...:AppClassLoader 或其子类
三、验证 classpath/ext 目录是否被篡改
检查 JVM 启动参数(可通过 jps -lvm 或查看进程启动命令):
# 典型 Spring Boot 启动命令中应不含: -Djava.ext.dirs=... # ⚠️ 若出现,说明人为重置了 ext 路径,边界已偏移 -Djava.endorsed.dirs=... # (已废弃,但仍有项目误用) # 应关注: -cp /path/to/app.jar # AppClassLoader 主力路径 -Dloader.path=lib/ # Spring Boot 特有,仍归 AppClassLoader 管理
若发现 -Djava.ext.dirs 被显式设置,哪怕只加了一个空格路径,JVM 就会完全忽略默认 $JAVA_HOME/jre/lib/ext,此时所有 ext 类必须出现在你指定的路径里,否则加载失败。
四、用 -verbose:class 观察实时加载过程
启动微服务时加上 JVM 参数:
-javaagent:your-agent.jar -verbose:class -XX:+TraceClassLoadingPreorder
启动后搜索关键词:
-
[Loaded javax.servlet.http.HttpServlet from ...]→ 查看from后的路径,匹配是否在lib/ext/下 -
[Loaded com.example.MyService from file:/app.jar!/BOOT-INF/classes/]→ 明确属于 AppClassLoader - 若某类报
ClassNotFoundException且路径明显在ext/下,但没被加载 → 检查该 JAR 是否缺失META-INF/MANIFEST.MF或签名异常(ext 目录下 JAR 有更严格校验)
? 小技巧:临时把一个测试类打包进
ext/xxx.jar,再启动并Class.forName("TestInExt"),看是否成功 —— 成功即证明 ExtensionClassLoader 生效且路径可达。
AppClassLoader 与 ExtensionClassLoader 的边界,不在代码里写死,而在 JVM 启动那一刻就由目录和参数划定了。真正要做的,是看清启动参数、确认物理路径、用 -verbose:class 验证行为 —— 不复杂但容易忽略细节。


















