Java类加载冲突中“重复加载”本质是同一全限定名类被不同ClassLoader加载,违反JVM链接约束,触发LinkageError等;需通过-verbose:class、Arthas、dependency:tree定位源头,并依Web/Spring Boot/多版本场景采取provided、exclusion或隔离类加载器等策略解决。

Java 类加载冲突中“重复加载”问题,本质是同一个全限定名的类被不同的 ClassLoader 加载了多次,违反 JVM 的链接约束,直接触发 LinkageError(如 LoaderConstraintViolation)或隐性导致 ClassCastException、NoSuchMethodError。排查和解决的关键不是“删哪个 jar”,而是搞清“谁在什么时候加载了哪个类”,再从构建期、运行时、类加载策略三层面控制确定性。
一、先确认是不是真被重复加载了
不能只看报错堆栈——它通常不显示加载器信息。必须验证实际加载行为:
- 启动时加
-verbose:class,重点关注报错类首次出现的位置,例如:[Loaded com.fasterxml.jackson.databind.ObjectMapper from file:/app/lib/jackson-databind-2.11.0.jar]
如果同一类名在日志中反复出现(尤其来自不同 jar 或不同 ClassLoader),就是重复加载信号。 - 线上环境用 Arthas 快速诊断:
sc -d com.example.YourClass查该类被哪些 ClassLoader 加载过;classloader -t看类加载器树,判断是否父加载器和子加载器各自加载了同一类。 - 写一行调试代码定位来源:
System.out.println(YourClass.class.getResource("YourClass.class"));
输出路径能直接告诉你这个类到底从哪来。
二、查清楚冲突源头在哪
重复加载很少凭空发生,背后必有依赖或部署问题:
- 执行
mvn dependency:tree -Dincludes=group:artifact,重点找同名 artifact 多版本共存(如 slf4j-api:1.7.30 和 slf4j-api:2.0.7);也留意shade插件是否把第三方类原样打进 fat jar,造成与外部依赖重复。 - 检查是否应用自己打包了本该由容器提供的类:比如 Tomcat 已带
servlet-api.jar,项目又把它的 jar 打进WEB-INF/lib/,就会导致 CommonClassLoader 和 WebAppClassLoader 各自加载一次。 - 看是否用了自定义 ClassLoader 却没遵守双亲委派:比如重写了
loadClass但没先调用super.loadClass,导致本该委托给父加载器的类被子加载器重复加载。
三、按场景选择解决方式
没有万能方案,要匹配你的部署环境和权限边界:
立即学习“Java免费学习笔记(深入)”;
-
Web 应用(Tomcat/Jetty):把所有业务依赖放在
WEB-INF/lib/,严禁放入$CATALINA_HOME/lib/或shared/lib;对 servlet-api、jsp-api 等容器 API,Maven 中声明<scope>provided</scope>,确保编译可用、运行时不打包。 -
Spring Boot 项目:用
<dependencyManagement>锁定 BOM 版本(如spring-boot-dependencies),避免传递依赖引入冲突版本;对已知问题依赖,用<exclusions>显式排除,例如:<exclusion><groupId>org.springframework</groupId><artifactId>spring-core</artifactId></exclusion> -
必须多版本共存(如旧模块用 POI 3.x,新功能需 POI 5.x 的公式引擎):不能靠改包名或删 jar,得用隔离方案——用
URLClassLoader或LaunchedURLClassLoader子类,为特定模块创建独立类加载器,并确保其加载路径不包含冲突 jar。
四、预防比修复更重要
日常开发中几个低成本习惯能大幅降低重复加载风险:
- IDE 中定期检查依赖顺序:IntelliJ → Project Structure → Modules → Dependencies,把核心依赖拖到顶部;Eclipse → Build Path → Order and Export,勾选并上移。
- CI 流程中加入
maven-enforcer-plugin,配置banDuplicateClasses规则,构建失败早于上线报错。 - 团队约定第三方工具包统一升级节奏,避免 A 模块升 Jackson 2.15,B 模块还卡在 2.9,最终打包时 Maven 自动仲裁出一个“谁都用不爽”的中间版本。


















