一、问题背景:模块化系统带来的限制
从 JDK 9 开始,Java 引入了 模块化系统(Java Platform Module System, JPMS),这是 Java 平台的一次重大架构升级。模块化系统的核心目标是:
- 强封装性:明确模块间的边界,限制对内部 API 的随意访问。
- 显式依赖:模块必须声明其依赖的其他模块,避免隐式依赖导致的兼容性问题。
这一变化直接导致了 JDK 9 及以上版本中,通过反射访问 JDK 内部非公开 API 时,会触发 “非法反射访问警告”(Illegal Reflective Access Warning)。这类警告的本质是:你的代码(或依赖的第三方库)试图绕过模块系统的访问控制,去操作本应被隐藏的 JDK 内部实现细节。
![图片[1]_解决JDK9及以上版本的非法反射访问警告问题_知途无界](https://zhituwujie.com/wp-content/uploads/2025/11/d2b5ca33bd20251124091155-1024x724.png)
二、典型警告示例
当你运行一个使用了反射(如通过 setAccessible(true) 访问私有字段/方法)的程序时,控制台可能会输出类似以下的警告:
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.example.MyClass (file:/path/to/your/jar.jar) to field java.util.Collections.EMPTY_MAP
WARNING: Please consider reporting this to the maintainers of com.example.MyClass
WARNING: Use --illegal-access=warn to enable warnings of this type
WARNING: Use --illegal-access=deny to disable them
关键信息解读:
- 触发源:
com.example.MyClass(你的代码或依赖库中的类)通过反射尝试访问java.util.Collections.EMPTY_MAP(JDK 内部的私有/内部字段)。 - 警告类型:非法反射访问(试图突破模块系统的封装限制)。
- 后续提示:建议向相关代码的维护者反馈此问题,并可通过 JVM 参数控制警告行为。
三、为什么会出现这个问题?
1. JDK 模块化的核心规则
在模块化系统中,每个模块(包括 JDK 自身的模块,如 java.base、java.util 等)都通过 module-info.java 明确声明了:
- 导出的包(exports):哪些包可以被其他模块访问(通常是公开的 API)。
- 开放的包(opens):哪些包允许其他模块通过反射访问(通常用于框架需要动态操作的场景,如 Spring 的 Bean 注入)。
默认情况下,JDK 内部未导出或未开放的包/类/方法/字段,对其他模块(包括你的应用代码)是不可见的。如果你的代码通过反射强行访问这些被隐藏的成员,就违反了模块系统的访问控制规则,从而触发警告。
2. 常见触发场景
- 直接操作 JDK 内部类:例如通过反射访问
sun.misc.Unsafe、java.util.Collections的内部字段(如EMPTY_MAP)、javax.management的内部实现等。 - 依赖的第三方库问题:许多老牌库(如早期版本的 Hibernate、Jackson、Log4j 等)为了兼容旧版 JDK,可能直接依赖了 JDK 内部的非公开 API(例如通过反射调用
sun.misc包下的工具类)。 - 动态代理或字节码增强:某些框架(如 Spring AOP、MyBatis)在运行时通过反射修改类的行为,若目标类属于 JDK 内部未开放的模块,也会触发警告。
四、解决方案:从临时规避到长期规范
方案 1:临时规避(不推荐长期使用)
如果只是希望消除警告(但不解决根本问题),可以通过 JVM 启动参数控制非法反射访问的行为。JDK 9+ 提供了 --illegal-access 参数,可选值如下:
| 参数值 | 行为说明 | 适用场景 |
|---|---|---|
permit(默认值,JDK 9~15) | 允许非法反射访问,但会打印警告(即你看到的 “Illegal reflective access”)。 | 临时测试或兼容旧代码时使用。 |
warn | 允许非法反射访问,且每次访问都打印警告(比 permit 更严格)。 | 需要明确知道哪些代码触发了警告时使用。 |
deny | 禁止非法反射访问,若尝试访问则直接抛出 InaccessibleObjectException。 | 强制要求代码合规(测试环境可用)。 |
quiet | 完全禁止警告(不推荐,会隐藏潜在问题)。 | 仅用于特殊场景(如 CI/CD 中屏蔽噪音)。 |
示例命令(通过 --illegal-access=permit 保持默认行为,或 --illegal-access=deny 强制拦截):
# JDK 9~15(默认 permit,显式声明可省略)
java --illegal-access=permit -jar your-app.jar
# JDK 16+(默认 deny,若需兼容旧代码可临时用 permit)
java --illegal-access=permit -jar your-app.jar
# 严格模式(禁止非法访问,适合测试合规性)
java --illegal-access=deny -jar your-app.jar
注意:从 **JDK 16 开始,
--illegal-access的默认值从permit改为deny**(但实际行为可能因版本略有差异)。长期来看,依赖此参数规避问题不可行,因为未来版本可能会彻底禁止非法访问。
方案 2:长期规范(推荐)—— 修复代码或升级依赖
(1)检查并修改自己的代码
如果警告是由你的业务代码触发的(例如通过反射调用了 Collections.EMPTY_MAP 或其他 JDK 内部字段),请 避免直接操作 JDK 的非公开 API,改用官方提供的公开方法。
常见错误示例:
// 错误:通过反射访问 JDK 内部字段(java.util.Collections.EMPTY_MAP 是内部常量)
Field emptyMapField = Collections.class.getField("EMPTY_MAP");
Map<?, ?> emptyMap = (Map<?, ?>) emptyMapField.get(null); // 触发非法反射警告
正确做法:直接使用 JDK 公开的 API(例如 Collections.emptyMap()):
// 正确:使用官方公开的静态方法
Map<?, ?> emptyMap = Collections.emptyMap(); // 无警告,安全可靠
(2)升级或替换有问题的第三方库
如果警告来自依赖的第三方库(例如老版本的 Hibernate、Jackson 等),请按以下步骤处理:
- 确认警告来源:通过警告信息中的类名(如
com.example.ThirdPartyClass)定位是哪个库触发的。 - 检查库的官方文档或 Issue:搜索该库是否已知存在 JDK 模块化兼容性问题(通常会在 GitHub Issues 或 Release Notes 中提及)。
- 升级到最新版本:大多数主流库(如 Spring Boot、Hibernate、Jackson 等)在 JDK 9+ 发布后已陆续修复了模块化兼容性问题,升级到最新稳定版通常可解决问题。
- 例如:Spring Boot 2.x+、Hibernate 5.3+、Jackson 2.10+ 均已适配模块化系统。
- 联系库的维护者:如果使用的是小众库且未更新,可向开发者提交 Issue,请求适配模块化系统。
如何快速定位问题库:
根据警告信息中的类名(如 com.example.ThirdPartyClass),通过 Maven/Gradle 的依赖树命令找到对应的库:
# Maven 项目
mvn dependency:tree
# Gradle 项目
gradle dependencies
然后在输出中搜索包含该类名的库,优先升级该库的版本。
方案 3:针对模块化项目的额外配置(可选)
如果你的应用本身是一个模块化项目(即包含 module-info.java 文件),可以通过 opens 或 exports 语句显式开放必要的包,但 通常不建议对 JDK 模块这样做(因为 JDK 内部 API 本就不应被外部依赖)。正确的做法仍是优先修复代码或升级依赖。
五、总结与最佳实践
| 场景 | 推荐方案 |
|---|---|
| 临时测试/兼容旧代码 | 使用 JVM 参数 --illegal-access=permit(JDK 9~15)或 --illegal-access=warn(观察具体触发源)。 |
| 生产环境/长期维护 | 优先修复自己的代码(避免反射访问 JDK 内部 API),升级依赖的第三方库 到适配模块化的版本。 |
| 严格禁止非法访问(如安全要求高) | 使用 --illegal-access=deny(JDK 16+ 默认行为),强制要求所有代码合规。 |
| 第三方库无法升级且必须使用 | 尝试通过 --add-opens 参数临时开放特定模块的包(不推荐,仅作最后手段)。 |
核心原则:JDK 模块化的目的是提升平台的安全性和可维护性,长期来看,依赖非法反射访问 JDK 内部 API 的代码必然会被淘汰。最佳实践是 遵循官方规范,使用公开的 API,及时升级依赖库,从根本上解决问题。

























暂无评论内容