问题重现
异常代码
2023-06-05T23:35:32.328+08:00 ERROR 6968 --- [nio-8080-exec-9] y.s.c.exception.ExceptionInterception : Unable to make protected native java.lang.Object java.lang.Object.clone() throws java.lang.CloneNotSupportedException accessible: module java.base does not "opens java.lang" to unnamed module @1757cd72
java.lang.reflect.InaccessibleObjectException: Unable to make protected native java.lang.Object java.lang.Object.clone() throws java.lang.CloneNotSupportedException accessible: module java.base does not "opens java.lang" to unnamed module @1757cd72
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354) ~[na:na]
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297) ~[na:na]
at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199) ~[na:na]
at java.base/java.lang.reflect.Method.setAccessible(Method.java:193) ~[na:na]
现在还有很长,不放了,大概意思就是这样
问题重点及分析
module java.base does not "opens java.lang" to unnamed module
翻译得:base 模块中没将 lang 模块打包到未命名模块。
说人话,人家不想给我们用 clone() 了,人家把 accessiable 属性设置为 false 了。
在 JDK1.8 及之前,这个属性是打开的,意思就是 1.8 前会反射出里面的所有方法。我们都知道反射需要大量的性能。然而这些方法的使用频率不高,为了这些调用可能性低的代码牺牲这些性能显然不值,何况有性能更高的方法来代替了这些方法,所以从 JDK1.8 后大量包的可访问反射属性都被关闭了,lang 就是其中之一。
这也就是很多 Java 工程以前明明用的好好的,升级后会报大量异常的原因。
问题证明
声明:出问题的异常的版本为 JDK17。
异常第二行中点击进去跳转到 AccessibleObject
的第 354 行,方法如下。
仔细分析会发现,在最后一个判断前,可设置访问属性全部都是返回true。
当上面都返回 false,第三个参数又为 true ,此时一定会报异常。
private boolean checkCanSetAccessible(Class<?> caller,
Class<?> declaringClass,
boolean throwExceptionIfDenied) {
if (caller == MethodHandle.class) {
throw new IllegalCallerException(); // should not happen
}
Module callerModule = caller.getModule();
Module declaringModule = declaringClass.getModule();
if (callerModule == declaringModule) return true;
if (callerModule == Object.class.getModule()) return true;
if (!declaringModule.isNamed()) return true;
String pn = declaringClass.getPackageName();
int modifiers;
if (this instanceof Executable) {
modifiers = ((Executable) this).getModifiers();
} else {
modifiers = ((Field) this).getModifiers();
}
// class is public and package is exported to caller
boolean isClassPublic = Modifier.isPublic(declaringClass.getModifiers());
if (isClassPublic && declaringModule.isExported(pn, callerModule)) {
// member is public
if (Modifier.isPublic(modifiers)) {
return true;
}
// member is protected-static
if (Modifier.isProtected(modifiers)
&& Modifier.isStatic(modifiers)
&& isSubclassOf(caller, declaringClass)) {
return true;
}
}
// package is open to caller
if (declaringModule.isOpen(pn, callerModule)) {
return true;
}
if (throwExceptionIfDenied) {
// not accessible
String msg = "Unable to make ";
if (this instanceof Field)
msg += "field ";
msg += this + " accessible: " + declaringModule + " does not \"";
if (isClassPublic && Modifier.isPublic(modifiers))
msg += "exports";
else
msg += "opens";
msg += " " + pn + "\" to " + callerModule;
InaccessibleObjectException e = new InaccessibleObjectException(msg);
if (printStackTraceWhenAccessFails()) {
e.printStackTrace(System.err);
}
throw e;
}
return false;
}
再看异常提示:module java.base does not "opens java.lang" to unnamed module
关于 module 的。
在方法里我们发现在 339 行判断了 module ,刚好那个检查的方法就叫 isOpen ,显然检测访问属性的这里。
通过打断点可以发现,Module
的 560 行调用了616 行的方法,很不幸所有能返回 true 的一个没踩到。
到此,完全可以证明 lang 包不被开放可访问属性。
解决方法
既然知道了问题,那简单,让他放开就可以了。但这个明显是关于 JVM 的问题,所以我们需要去 JVM 里调参。那简单点启动时给他加上这个开放属性就可以了。
具体如下
1
2
3
代码在这里
–add-opens java.base/java.lang=ALL-UNNAMED
至此,这个问题已解决。再测试,clone() 方法已经正常用了。
总结
一个问题是由多方位造成的,写了这么多不仅仅只是想让大家知道这个问题如何解决,更想传递一种定位问题、解决问题的方法。
行成于思,善于思考才是最重要的!!