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-devanswered的回复。
    对于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

    08-04 05:59