JDK9模块化系统未推翻双亲委派模型,而是以模块归属判断为第一道筛选,优先查模块再定加载器;模块路径(--module-path)成为新主干,支持具名、自动、匿名三类模块;类加载器分工更清晰且无继承关系,通过显式协作实现模块化加载。
java 9 引入模块系统(jigsaw)后,类加载器的双亲委派机制和变量寻址逻辑发生了实质性演进——不是简单叠加,而是结构重定义。核心变化在于:类加载器职责被模块化分层接管,传统“三层委派链”退居后台,模块路径(--module-path)与模块导出/开放规则成为寻址前提。
模块化如何重构类加载器层级
在 JDK 9+ 中,启动类加载器(Bootstrap)、平台类加载器(Platform,原 Extension)和应用类加载器(System)仍存在,但关系已非线性委派:
- 平台类加载器不再加载
lib/ext下的 JAR,而是加载JAVA_HOME/lib中的模块化 JAR(如java.base、java.sql),且其本身由 JVM 内部模块系统驱动 - 应用类加载器默认变为
jdk.internal.loader.ClassLoaders.AppClassLoader,但它只负责加载未打包为模块的类(即传统 classpath 类);若类属于命名模块,则由模块系统统一调度 - 新增 模块化类加载器(ModuleLayer.Loader):每个模块层(
ModuleLayer)拥有独立的模块解析上下文,类加载请求先经模块图(ModuleDescriptor)验证是否可读(requires)、是否可访问(exports/opens)
变量寻址逻辑从“类路径优先”转向“模块图可达”
静态变量、常量池符号引用、甚至 Class.forName() 的解析,不再仅依赖类加载器委托链,而需满足模块可见性约束:
-
静态字段访问失败常见原因:即使类已加载,若调用方模块未声明
requires目标模块,或目标模块未exports包含该字段的包,JVM 抛出IllegalAccessError(而非NoClassDefFoundError) -
反射访问受控升级:过去可通过
setAccessible(true)绕过包级访问限制;模块化后,若目标模块未opens对应包给调用方模块(或ALL-UNNAMED),反射将直接失败 -
服务加载器(SPI)行为变更:
ServiceLoader.load()默认使用线程上下文类加载器(TCCL),但模块系统要求服务提供方模块必须provides接口,且调用方模块uses该接口,否则服务不可见
打破委派的典型场景与适配要点
模块化并未废弃双亲委派,而是将其嵌入模块解析流程中。真正“破坏”的是旧有 classpath 意义上的自由加载:
- 自定义类加载器若需加载模块类,必须通过
ModuleFinder和Configuration构建新ModuleLayer,再由其defineModule()注册,不能直接defineClass() -
--add-opens和--add-exports是运行时补救手段,本质是动态修改模块描述符,不改变加载器委派路径,但覆盖模块图的默认访问策略 - 混合模式(modular + unnamed)下,未命名模块(classpath 类)自动读取所有命名模块,但反向不成立;此时变量寻址需显式导出,否则跨模块静态字段访问失败
模块化下的类加载,关键已不在“谁来加载”,而在“谁被允许看见”。双亲委派仍在底层流转,但前置校验从类路径扫描,变成了模块依赖图的拓扑遍历。

















