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
}
}
答案 0 :(得分:6)
这似乎是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
(请注意其中的注释,该注释向我们显示了用于链接方法的常量池中方法引用常量的签名)
因此,在运行时,虚拟机似乎会尝试找到一种以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个错误;
,错误地链接了签名多态方法。我还可以使用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。