Java中类不会被重复加载,前提是使用同一个类加载器;其依赖双亲委派模型与ClassLoader内部的缓存机制(以全限定名+加载器为键),命中缓存则直接返回Class对象,未命中才执行后续加载流程。

Java 中类不会被重复加载,前提是使用同一个类加载器。这是由 JVM 类加载的双亲委派模型和类加载缓存机制共同保证的——每个类加载器内部维护一个 ConcurrentHashMap(或类似结构),以 类全限定名 + 类加载器实例 为唯一键,已加载的 Class 对象会被缓存,后续相同请求直接返回缓存结果,不会重新解析、链接、初始化。
类加载器的缓存机制怎么工作的
每个 ClassLoader 子类(包括 AppClassLoader、ExtClassLoader,以及自定义类加载器)都持有自己的命名空间。关键在于:同一个类加载器对同一个全限定类名只会加载一次。JVM 在调用 loadClass(String name) 时,会先查本地缓存(如 classes 字段或通过 findLoadedClass(name)),命中则直接返回;未命中才走双亲委派、查找字节码、调用 defineClass 完成加载。
-
注意:不同类加载器即使加载同一个 .class 文件,也会生成不同的
Class实例,它们在 JVM 中被视为完全无关的类型(比如无法强制转型、无法共享静态变量)。 -
验证方式:可通过
MyClass.class.getClassLoader() == otherClass.getClassLoader()判断是否由同一加载器加载;再结合MyClass.class == otherClass.class可确认是否为同一个 Class 对象。
怎么判断某个类是否已被当前类加载器加载
标准做法是调用类加载器的 findLoadedClass(String name) 方法。该方法是 protected 的,只能在自定义类加载器内部或其子类中直接调用;若在外部想检测,可借助反射或更稳妥的方式:
- 反射调用(不推荐生产环境):
loader.getClass().getMethod("findLoadedClass", String.class).invoke(loader, "com.example.Foo") - 更实用的办法:尝试用
Class.forName("com.example.Foo", false, loader),第二个参数设为false表示不触发初始化;如果类已加载,会直接返回缓存的Class;如果未加载且找不到类文件,则抛ClassNotFoundException;但注意它仍可能触发加载(当类尚未加载且字节码可定位时)。 - 最安全的检测逻辑:捕获
ClassNotFoundException并配合日志或调试器观察ClassLoader的classes字段(需调试模式或 JMX 工具)。
为什么有时看起来“类被重复加载”了
实际并非重复加载,而是以下常见误解场景:
立即学习“Java免费学习笔记(深入)”;
-
多个类加载器实例:比如 OSGi、Tomcat 每个 WebApp 使用独立
WebAppClassLoader,同一份 class 文件被不同加载器各加载一次,产生多个互不兼容的Class对象。 - 热部署/重载机制:像 Spring Boot DevTools 或 JRebel,并非“重复加载”,而是卸载旧类加载器、新建加载器再加载新字节码——旧 Class 被 GC,新 Class 是全新对象。
-
误判 Class 对象相等性:写了
obj instanceof MyClass却抛ClassCastException,很可能是因为obj的MyClass是另一个类加载器加载的,此时两个MyClass.class不等价。
基本上就这些。核心记住一点:JVM 层面天然防重复加载,问题往往出在类加载器隔离或开发工具机制上,而不是 JVM 自身失效。










