
java 类加载器遵循双亲委派模型,但当使用独立的 urlclassloader 实例时,即使指向相同 jar 文件,也会生成彼此隔离的类实例——因为类的唯一性由“全限定名 + 加载它的 classloader”共同决定。
在 Java 中,一个类是否“相同”,不仅取决于其全限定名(如 example.ToBeLoaded),更关键的是加载它的 ClassLoader 实例。JVM 在运行时将 Class 对象视为“由特定 ClassLoader 定义的类型”,即使两个 Class 对象字节码完全一致、名称完全相同,只要它们由不同的 ClassLoader 加载,JVM 就会视其为两个完全无关、互不兼容的类型。
这正是你示例中输出 false false 的根本原因:
System.out.println(child2 == child1); // false —— 不是同一个 Class 对象引用 System.out.println(child2.equals(child1)); // false —— Class.equals() 内部也基于 classloader 判等
虽然 URLClassLoader 默认以 AppClassLoader(即系统类加载器)为父加载器,但双亲委派仅影响“加载行为”的发起顺序,而非强制复用已加载的类。关键在于:cl1 和 cl2 是两个独立的 URLClassLoader 实例,它们各自维护私有的已定义类缓存(definedClasses)。当调用 cl1.loadClass("example.ToBeLoaded") 时:
- cl1 先委托给父加载器(AppClassLoader)尝试加载;
- 若父加载器未找到该类(例如 ToBeLoaded 不在 classpath 中),则 cl1 自行从指定 JAR 中读取字节码并调用 defineClass() 创建新 Class 对象;
- 同理,cl2 执行相同流程,得到另一个独立的 Class 实例。
⚠️ 注意:双亲委派 ≠ 类共享。只有当父加载器成功返回一个 Class 实例(即该类已被父加载器加载过),子加载器才不会重复定义——而你的场景中,AppClassLoader 并未加载 ToBeLoaded,因此委派失败,cl1 和 cl2 各自完成定义,结果是两个隔离的类。
立即学习“Java免费学习笔记(深入)”;
这种机制并非缺陷,而是关键设计特性,广泛应用于:
- Java EE / Jakarta EE 容器(如 Tomcat、WildFly):每个 Web 应用拥有独立 WebAppClassLoader,支持不同应用依赖同一类名但不同版本的库(如 commons-lang3:3.9 vs 3.12),避免冲突;
- OSGi 框架:实现细粒度模块隔离与热部署;
- 插件化系统(如 IDE 插件、Jenkins 插件):保障插件间类空间独立,防止污染主程序。
✅ 正确判断“两个 Class 是否等价”的方式是:
boolean sameClass = child1 == child2; // 必须是同一对象引用(最严格)
// 或更实用的运行时检查:
boolean sameType = child1.getClassLoader() == child2.getClassLoader()
&& child1.getName().equals(child2.getName());❌ 错误做法:仅比较类名、包名,或试图通过 instanceof 跨类加载器判别(将抛 ClassCastException)。
总结:Java 的类唯一性模型是 (className, ClassLoader) 二元组。理解这一点,是掌握模块化、容器化、热部署等高级特性的基石。










