JDK 9+ 中 setAccessible(true) 抛 InaccessibleObjectException 是因 JPMS 模块封装限制,需用 --add-opens 显式打开目标包(如 --add-opens java.base/jdk.internal.misc=ALL-UNNAMED),且必须作为 JVM 启动参数正确配置。

为什么 setAccessible(true) 在 JDK 9+ 里直接报 InaccessibleObjectException
不是代码漏写了 setAccessible(true),而是 JVM 拦在了模块边界上。JDK 9 引入 JPMS 后,java.base 这类核心模块默认不 opens 内部包(比如 jdk.internal.misc 或 sun.misc),哪怕你调了 setAccessible(true),checkCanSetAccessible() 也会在运行时拒绝——抛 InaccessibleObjectException,不是警告,是硬失败。
典型错误信息长这样:InaccessibleObjectException: Unable to make static jdk.internal.misc.Unsafe jdk.internal.misc.Unsafe.getUnsafe() accessible: module java.base does not "opens jdk.internal.misc" to module your.app。
关键点:这个检查发生在反射对象调用 setAccessible(true) 的瞬间,和你是否在同一个包、是否有权限修饰符无关,纯模块级封锁。
必须用 --add-opens 显式打开目标包,且粒度要准
--add-opens 是唯一可靠的运行时解法,但它不能笼统写模块名,必须精确到「源模块/包 = 目标模块」。
- 查清被反射的类在哪:比如
ArrayList.elementData属于java.util包,而该包在java.base模块里 → 需加--add-opens java.base/java.util=ALL-UNNAMED - 若反射的是
Unsafe,路径是jdk.internal.misc.Unsafe→ 对应--add-opens java.base/jdk.internal.misc=ALL-UNNAMED - 多个包要放开?重复写多条参数,不能合并或通配:
--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED - 如果你的应用用了命名模块(如
module com.example.app),把ALL-UNNAMED换成你的模块名更安全,避免污染其他 unnamed 模块
IDE 和构建工具里加参数,只加对地方才生效
--add-opens 是 JVM 启动参数,不是编译参数,塞错位置等于没加。
- IntelliJ IDEA:Run → Edit Configurations → 「VM options」输入框里粘贴,别填在「Program arguments」里
- Maven 打包后运行:得在执行
java -jar时加,不是maven-compiler-plugin的compilerArgs - Spring Boot 应用:如果用
mvn spring-boot:run,需配置spring-boot-maven-plugin的jvmArguments;如果打成 fat jar,则启动命令要显式带参数:java --add-opens java.base/java.util=ALL-UNNAMED -jar app.jar - Gradle:在
runtask 的jvmArgs里加,不是compileJava.options.forkOptions.jvmArgs
jar 包内 internal 类的问题,本质是依赖方没声明 opens
如果你发布的 jar 被别人引用,并触发了非法反射访问,不能指望对方去加 JVM 参数——那是强耦合且不可控的。
真正可维护的做法是:
- 避免直接反射 JDK internal 类(如
Unsafe、SharedSecrets)。改用标准 API,比如用VarHandle替代部分Unsafe场景 - 若必须兼容旧逻辑,把反射封装进自己的模块,并在
module-info.java里主动opens对应包(仅限你控制的模块) - 对外提供 package-private 方法 + 同包测试类,比跨模块反射干净得多
- 用
@VisibleForTesting标记临时暴露点,同时留下重构线索
模块系统不会自动帮你绕过封装,它只是把过去“能跑就行”的侥幸,变成了“必须说清楚谁允许谁访问”的契约。越晚面对这个契约,升级 JDK 时踩的坑就越深。











