java 9+ 通过jpms强封装jdk内部包,需用--add-exports(导出包)、--add-opens(开放反射)、--add-reads(建立模块读取关系)在jvm启动时绕过限制,但存在兼容性风险。

Java 从 9 开始引入模块系统(JPMS),默认对 JDK 内部包(如 jdk.internal.*、sun.misc.*)实施强封装——即使类是 public 的,只要所在包未被导出或打开,外部模块或类路径代码就无法访问,运行时会抛出 IllegalAccessError 或反射失败。要临时绕过这一限制,必须在 JVM 启动时使用特定命令行参数,不能在代码中动态生效。
--add-exports:让包“可见”
适用于需要直接引用某模块中 public 类的场景,比如调用 jdk.internal.ref.Cleaner 的静态方法。
- 语法格式:
--add-exports /= - 对类路径(传统 jar)代码开放:用
ALL-UNNAMED,例如--add-exports java.base/jdk.internal.ref=ALL-UNNAMED - 只对指定命名模块开放:如你的模块名为
my.app,需访问java.desktop中的内部 Swing 包:--add-exports java.desktop/com.sun.java.swing.plaf.windows=my.app - 多个包需多次写该参数,不能合并:
--add-exports java.base/jdk.internal.ref=ALL-UNNAMED --add-exports java.desktop/sun.awt=ALL-UNNAMED
--add-opens:支持反射访问
当代码用 setAccessible(true) 访问非 public 字段/方法(如 Unsafe.theUnsafe),或框架(Hibernate、JUnit、Spring)依赖深层反射时,--add-exports 不够,必须用 --add-opens。
- 它不仅让类可见,还打开包的反射权限,是 JDK 16+ 推荐的替代方案
- 格式类似:
--add-opens /= - 常见写法:
--add-opens java.base/java.lang=ALL-UNNAMED--add-opens java.base/jdk.internal.reflect=ALL-UNNAMED - 注意:JDK 16 起默认禁用非法反射,若还加了
--illegal-access=permit,建议逐步迁移到--add-opens
--add-reads:解决模块“不可见”问题
有时报错不是因为包没导出,而是当前模块根本“不认识”目标模块(即没声明 requires)。比如你的模块没声明依赖 java.xml,却尝试加载其类,就会提示 Module not found。
- 用
--add-reads强制建立读取关系:--add-reads my.app=java.xml - 也可开放给所有模块:
--add-reads ALL-UNNAMED=java.xml - 这个参数不涉及封装,只解决模块图解析阶段的可见性
实际启动与注意事项
这些参数必须作为 JVM 启动选项传入,位置在 -cp 或 --module-path 之后、主类名之前:
- 命令行示例:
java --add-exports java.base/jdk.internal.ref=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED -cp app.jar MyApp - Maven 测试时,在
maven-surefire-plugin的<jvmargs></jvmargs>里配置 - IDE(如 IntelliJ):Run Configuration → VM Options 中填写
- Windows 下命令行超长(>8191 字符)?改用
@argfile方式,把参数写进文本文件再引用 - ⚠️ 风险提示:依赖内部 API 本质不稳定。JDK 可能在任意版本移除或修改这些类,生产环境应尽快迁移到标准 API 或官方替代方案
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











