子加载器可访问父加载器加载的类,但父加载器不可访问子加载器加载的类,这是由类加载器树状层级结构、单向引用关系及安全隔离需求共同决定的必然结果。

子加载器能访问父加载器加载的类,但父加载器不能访问子加载器加载的类——这不是设计上的限制,而是类加载器层级结构和安全模型共同决定的必然结果。
可见性本质是单向依赖关系
类加载器之间形成树状层级,子加载器显式持有对父加载器的引用(parent 字段),而父加载器完全不知道子加载器的存在。这种结构天然支持“向上可见”:当子加载器需要某个类时,它会先委托父加载器去加载;一旦父加载器成功返回 Class 对象,该对象就进入子加载器的作用域,可被正常使用。
反过来说,父加载器既没有子加载器的引用,也没有机制去探测或拉取子加载器加载的类。即使强行绕过委托机制让子加载器提前加载了某个类,父加载器的 findLoadedClass 也查不到它——因为每个加载器只维护自己加载过的类缓存。
为什么不允许反向访问?核心是隔离与安全
-
防止污染核心类库:如果系统类加载器(AppClassLoader)能随意使用由 Web 容器自定义加载器加载的
java.util.ArrayList,那 JDK 的基础行为就可能被篡改,整个 JVM 稳定性崩溃。 - 保障模块边界:在 OSGi、Spring Boot DevTools 或 Tomcat 中,不同应用/模块需彼此隔离。允许父加载器访问子加载器的类,等于打破沙箱,导致类版本冲突、静态变量共享异常等严重问题。
- 避免循环依赖风险:若双向可见,A 加载器加载的类依赖 B 加载器的类,B 又反过来依赖 A 的类,就可能陷入无法解析的死锁。
实际表现:Class.forName 和 getClassLoader 的印证
假设你用自定义加载器 MyClassLoader 加载了 com.example.Service:
- 该类内部可以正常 new
java.lang.String(由 Bootstrap 加载)、调用java.util.List(由 AppClassLoader 加载)——因为它的加载器能沿 parent 链向上找到这些类。 - 但如果你在 main 方法里(由 AppClassLoader 执行)写
Class.forName("com.example.Service"),会抛 ClassNotFoundException——AppClassLoader 不知道、也无法访问 MyClassLoader 加载的类。 - 反过来,
Service.class.getClassLoader()返回的是 MyClassLoader 实例,而它的 getParent() 是 AppClassLoader,再往上是 ExtClassLoader,最后是 null(Bootstrap)——这条链清晰体现单向可达性。
打破可见性不是“修复”,而是有代价的权衡
某些场景(如热部署、插件化)确实需要子类“透出”给父环境,但这必须主动设计:
- 通过接口抽象:把服务定义为 JDK 或系统类加载器可见的接口(如
java.util.ServiceProvider),实现类由子加载器加载,由父加载器通过 SPI 机制发现——接口可见,实现不可见,靠契约解耦。 - 线程上下文类加载器(TCCL):显式设置
Thread.currentThread().setContextClassLoader(myLoader),让框架代码(如 JNDI、JAXB)临时切换加载视角。但这属于“借用”而非“继承”,且需手动管理,容易引发内存泄漏。

















