Java自定义类加载器的命名空间是继承式叠加结构,由自身及所有父加载器共同构成;子加载器可见父加载器的类,父加载器不可见子加载器的类,无父子关系的加载器互不可见同名类。

Java 类加载过程中,自定义类加载器的命名空间由它自身及其所有父加载器共同构成,不是独立隔离的“私有空间”,而是继承式叠加的结构。理解这一点,是避免 ClassCastException、静态字段不共享、类无法识别等运行时问题的关键。
命名空间的本质是加载器链的可见性边界
每个类加载器实例(哪怕同类型、同路径)都代表一个独立的命名空间起点。这个空间包含:它自己加载的所有类 + 所有祖先加载器加载的类。也就是说:
- 子加载器能看到父加载器加载的类(例如自定义加载器能用
java.lang.String); - 父加载器看不到子加载器加载的类(系统类加载器无法访问你 new 出来的
MyClassLoader加载的com.example.Service); - 两个无父子关系的自定义加载器(比如两个独立 new 的
URLClassLoader),它们加载的同名类互不可见,会被 JVM 视为完全不同的类型。
自定义加载器必须显式指定 parent 才能正确融入命名空间体系
如果不指定 parent,构造时默认使用系统类加载器(AppClassLoader)作为父加载器。但很多场景需要更精确控制,比如插件隔离或热替换:
- 想复用已加载的核心类(如 Spring、SLF4J),就让自定义加载器以
Thread.currentThread().getContextClassLoader()为 parent; - 想彻底隔离(如 OSGi bundle),可传入
null(此时 parent 为 Bootstrap 加载器,但实际受限于双亲委派,通常仍需配合绕过逻辑); - 多个插件加载器共用同一 parent,才能让它们共享该 parent 加载的公共依赖类。
绕过双亲委派时,命名空间风险陡增
重写 loadClass() 并跳过 super.loadClass() 调用,等于切断了与父空间的连接通道。这时:
立即学习“Java免费学习笔记(深入)”;
- 同一个类(如
org.apache.commons.lang3.StringUtils)可能被不同自定义加载器重复加载多次; - 即使字节码完全相同,JVM 也认为它们是不同类,导致强制转型失败、单例失效、序列化异常;
- 推荐只在
findClass()中加载业务类,把框架类、基础类仍委托给 parent —— 这才是安全打破委派的方式。
判断类是否“同源”的唯一可靠方式是比对 ClassLoader 引用
运行时排查命名空间问题,不能看类名、包名甚至字节码内容,而要直接比较加载器引用:
正确做法:obj1.getClass().getClassLoader() == obj2.getClass().getClassLoader()
错误做法:
用 equals() 比较加载器(Tomcat 等容器会重写 equals)、或仅比对 getClass().getName()


















