VarHandle显示以下错误-
Exception in thread "main" java.lang.NoSuchMethodError: VarHandle.compareAndSet(VarHandleExample,int,int)void
at java.base/java.lang.invoke.MethodHandleNatives.newNoSuchMethodErrorOnVarHandle(MethodHandleNatives.java:492)
at java.base/java.lang.invoke.MethodHandleNatives.varHandleOperationLinkerMethod(MethodHandleNatives.java:445)
at java.base/java.lang.invoke.MethodHandleNatives.linkMethodImpl(MethodHandleNatives.java:378)
at java.base/java.lang.invoke.MethodHandleNatives.linkMethod(MethodHandleNatives.java:366)
at j9.VarHandleExample.update(VarHandleExample.java:23)
at j9.VarHandleExample.main(VarHandleExample.java:14)
我的程序是:
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
public class VarHandleExample {
public int publicTestVariable = 10;
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
VarHandleExample e= new VarHandleExample();
e.update();
}
public void update() throws NoSuchFieldException, IllegalAccessException {
VarHandle publicIntHandle = MethodHandles.lookup()
.in(VariableHandlesTest.class)
.findVarHandle(VarHandleExample.class, "publicTestVariable", int.class);
publicIntHandle.compareAndSet(this, 10, 100); // CAS
}
}
最佳答案
这似乎是JVM / JDK / Spec / Doc中的错误,这取决于编译器如何翻译签名多态方法的签名。compareAndSet
标记为@MethodHandle.PolymorphicSignature
。从语义上(解释)意味着,在调用站点上找到的任何签名都将用于调用该方法。这主要是防止对参数进行装箱。
VarHandle中compareAndSet
的完整签名为:
public final native
@MethodHandle.PolymorphicSignature
@HotSpotIntrinsicCandidate
boolean compareAndSet(Object... args);
请注意,它返回了boolean
,但该错误向我们显示了VM正在尝试链接具有不同返回类型的VarHandle.compareAndSet(VarHandleExample,int,int)void
。这也可以从Eclipse编译器生成的字节码中看到:publicIntHandle.compareAndSet(this, 10, 100); // CAS
被(部分)翻译为:25: invokevirtual #55 // Method java/lang/invoke/VarHandle.compareAndSet:(LVarHandleExample;II)V
(请注意其中的注释,该注释向我们显示了用于链接方法的常量池中方法引用常量的签名)因此,在运行时,VM似乎会尝试找到一种使用
V
(即void
)作为返回类型的方法,实际上这是不存在的。另一方面,
javac
生成此签名:25: invokevirtual #11 // Method java/lang/invoke/VarHandle.compareAndSet:(LVarHandleExample;II)Z
返回类型是Z
(意思是boolean
)而不是V
。您可以通过使用返回值明确地使返回类型为
boolean
来解决此问题:boolean b = publicIntHandle.compareAndSet(this, 10, 100); // CAS
或者,如果不需要该值,请使用空白的if
:if(publicIntHandle.compareAndSet(this, 10, 100)); // CAS
现在进入语言律师部分。
我可以找到有关签名多态方法(标有@PolymorphicSignature的方法)[1](语言规范中没有)的有限信息。对于规范中的编译器应如何导出和翻译签名多态方法的描述符,似乎没有任何要求。
也许最有趣的是jvms-5.4.3.3(强调我的)的这段话:
如果C完全使用方法引用指定的名称声明了一个方法,并且声明是签名多态方法(§2.9.3),则方法查找成功。描述符中提到的所有类名都将被解析(第5.4.3.1节)。
解析的方法是签名多态方法声明。 C不必使用方法参考指定的描述符声明方法。
在这种情况下,
C
将为VarHandle
,要查找的方法将为compareAndSet
,并且描述符((LVarHandleExample;II)Z
或(LVarHandleExample;II)V
)取决于编译器。关于Signature Polymorphism的javadoc也很有趣:
当JVM处理包含签名多态调用的字节码时,它将成功链接任何此类调用,而不考虑其符号类型描述符。 (为了保持类型安全,JVM将通过适当的动态类型检查来保护此类调用,如其他地方所述。)
VarHandle
确实有一种名称为compareAndSet
的方法,并且它是签名多态的,因此查找应该成功。恕我直言,VM的问题是在这种情况下会引发异常,因为描述符和返回类型根据规范都不重要。javac在描述符中发出
Z
作为返回类型似乎也存在问题。根据同一javadoc部分:不寻常的部分是符号类型描述符是从实际参数和返回类型派生的,而不是从方法声明派生的。
但是,javac发出的描述符肯定取决于方法声明。
根据规范/文档,这里似乎有2个bug;
您正在使用的VM中的
OpenJDK 64-Bit Server VM (build 13-internal+0-adhoc.Jorn.jdk, mixed mode, sharing)
来重现它,这是最新的OpenJDK源代码。javac
中的我以规范为主要权限,但是在这种情况下,规范/文档在实现之后才没有更新的可能性更大,这就是令人讨厌的eclipsec。
我收到了Dan Smith对email to jdk-dev和answered的回复。
对于2.)
javac在这里是正确的。参见jls-15.12.3-400-B
“如果签名多态方法为void或具有除Object以外的返回类型,则编译时结果是编译时声明的调用类型的结果”
您引用的javadoc中的非正式描述不完整,我已经提交了一个错误来解决此问题:https://bugs.openjdk.java.net/browse/JDK-8216511
因此,看来Eclipse为该调用而不是javac生成了错误的描述符。
对于1.)
你是对的。此处未指定链接时NoSuchMethodError。而是,根据VarHandle javadoc,我们应该看到运行时WrongMethodTypeException。
错误报告:https://bugs.openjdk.java.net/browse/JDK-8216520