Java中同一类被不同类加载器加载,本质是“身份分裂”,JVM视其为两个不同类;解决核心是明确意图:要么通过统一加载器避免分裂,要么用独立加载器+接口契约主动隔离,并将共享状态外移至类加载器无关层级。

Java 中同一个类被不同类加载器加载,不是“重复”问题,而是“身份分裂”——JVM 认为它们是两个完全不同的类。处理核心不是阻止加载,而是明确意图:要么避免分裂,要么主动隔离。
先确认是不是真问题
别一看到 ClassCastException: com.example.User cannot be cast to com.example.User 就动手改代码。先验证:
- 在出错位置打印:
obj.getClass().getClassLoader()和User.class.getClassLoader() - 加 JVM 参数
-verbose:class,启动时 grep 目标类名,看是否被多个加载器加载过 - 用 Arthas 执行
sc -d com.example.User,查它被哪些 ClassLoader 实例定义过
想统一加载:堵住绕过双亲委派的入口
多数冲突源于自定义类加载器没走委派,或上下文类加载器(TCCL)被误用:
- 检查所有自定义
ClassLoader子类,确保loadClass方法里调用了super.loadClass(name) - 避免在 Servlet、Filter 或 Spring Bean 初始化中 new 自定义加载器去加载业务类
- 排查
Class.forName(className, true, cl)调用,确认传入的cl是系统类加载器或与当前环境一致 - Spring Boot DevTools 的
RestartClassLoader会隔离类,开发期可临时排除该依赖
想隔离共存:用独立加载器+接口契约
当必须同时使用两个版本(如插件系统),就得主动打破委派,但需严格约束边界:
立即学习“Java免费学习笔记(深入)”;
- 继承
URLClassLoader,重写loadClass,跳过super.loadClass(),只调用findClass - 把不同版本 JAR 放在互不重叠路径下(如
/plugins/v1/xxx.jar和/plugins/v2/xxx.jar) - 为每个版本创建独立的加载器实例,且彼此无父子关系
- 对外暴露统一接口(如
public interface DataProcessor),两个版本各自实现它,业务代码只依赖接口 - 执行前显式设置 TCCL:
Thread.currentThread().setContextClassLoader(pluginLoader)
共享状态不能放 static 变量里
static 字段属于类,而类属于加载器。跨加载器共享数据,必须移出类级别:
- 用 JVM 进程级机制:
java.util.concurrent.ConcurrentHashMap配合类名作 key,或用Unsafe操作静态字段(慎用) - 用外部存储:Redis、数据库、文件,或通过 JNDI 绑定全局对象
- Web 应用中可将共享状态放在
ServletContext或 Tomcat 的SharedClassLoader加载的类里


















