之前使用jdk8的代码升级到jdk17运行报错信息如下:
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make field private final byte[] java.lang.String.value accessible: module java.base does not "opens java.lang" to unnamed module @473b46c3at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178)at java.base/java.lang.reflect.Field.setAccessible(Field.java:172)
大致意思就是通过反射获取String类中的私有字段byte[] value 失败了。
那怎么会这样呢,原因是:
在Java9之后引入了module模块的概念,而不同module模块间是不允许使用反射来访问非public的字段/方法/构造函数(field/method/constructor),除非被访问的目标模块为反射设置open即允许自身被其他模块进行non-public反射访问。
解决办法:
Java 9之后引入了一个新的JVM选项 --illegal-access,该选项有四个可选值:
permit:Jdk9、Jdk11的默认值,允许不同模块间进行non-public反射访问,且仅在首次非法访问会给出警告,后续不再警告。在该模式下会自动将Jdk8(或更低版本)的代码进行opens设置,即允许Jdk8的代码被其他模块进行non-public反射访问,也允许Jdk8的代码对其他模块进行non-public反射访问,如此可保证高、低版本混合的Java程序如往常一样正常运行。
warn:类似permit,但是每次非法访问都会警告
debug:类似warn,但在warn的基础上加入了类似e.printStackTrace()的功能
deny:未来的默认值,禁止所有的不同模块间的non-public反射访问,出现非法反射则抛出异常,除了使用特别的命令行参数排除的模块,比如使用 –add-opens排除某些模块使其能够通过非法反射访问
线上使用了--illegal-access=deny,所以出现非法反射时会导致程序抛异常而启动失败,
而本地开发环境运行程序时,未明确设置–illegal-access则使用默认--illegal-access=premit,所以可以启动成功,但在第一次非法反射访问时给出了警告。
综上,在设置了--illegal-access=deny(推荐设置deny,兼容未来Java版本)时,需同时添加--add-opens以开启对应模块/包允许被其他模块进行非法(non-public)反射访问。
例如根据之前的日志:
Unable to make field private final byte[] java.lang.String.value accessible:
module java.base does not "opens java.lang" to unnamed module @1b70203f
将关键提示日志拆解后如下表:
被访问模块名 | 被访问包名 | 发起非法访问的模块名 | |||
---|---|---|---|---|---|
module | java.base | does not | “opens java.lang” | to | unnamed module @473b46c3 |
具体转换格式:--add-opens 被访问模块名/被访问包名=发起非法访问的模块名
根据如上日志转换如下--add-opens命令为:
--add-opens java.base/java.long=ALL-UNNAMED
注: ALL-UNNAMED表示所有的未命名模块
反复重启根据提示最终整理如下完整--add-opens选项:
--illegal-access=deny--add-opens java.base/java.lang=ALL-UNNAMED--add-opens java.base/java.lang.reflect=ALL-UNNAMED--add-opens java.base/java.lang.invoke=ALL-UNNAMED--add-opens java.base/java.math=ALL-UNNAMED--add-opens java.base/java.util=ALL-UNNAMED--add-opens java.base/java.util.concurrent=ALL-UNNAMED--add-opens java.base/java.net=ALL-UNNAMED--add-opens java.base/java.text=ALL-UNNAMED
如上完整启动选项添加到vm参数中后,程序可以正常启动了。