8

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
    }
}
4

1 回答 1

6

这似乎是 JVM/JDK/Spec/Doc 中的一个 bug,这取决于编译器如何翻译签名多态方法的签名。


compareAndSet标有@MethodHandle.PolymorphicSignature。这意味着语义(解释)在调用站点找到的任何签名都将用于调用该方法。这主要是防止参数的装箱。

VarHandle的完整签名compareAndSet是:

public final native
@MethodHandle.PolymorphicSignature
@HotSpotIntrinsicCandidate
boolean compareAndSet(Object... args);

请注意,它返回 a 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(ie 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取决于编译器。

同样有趣的是关于签名多态的javadoc :

当 JVM 处理包含签名多态调用的字节码时,它将成功链接任何此类调用,而不管其符号类型描述符如何。(为了保持类型安全,JVM 将使用适当的动态类型检查来保护此类调用,如其他地方所述。)

VarHandle确实只有一个方法的名称compareAndSet,它是签名多态的,所以查找应该成功。恕我直言,在这种情况下抛出异常是VM的问题,因为描述符,因此根据规范,返回类型应该无关紧要。

Zjavac作为描述符中的返回类型发出似乎也存在问题。根据相同的 javadoc 部分:

不寻常的部分是符号类型描述符是从实际参数和返回类型派生的,而不是从方法声明中派生的。

但是,javac 发出的描述符肯定取决于方法声明。

因此,根据规范/文档,这里似乎有 2 个错误;

  1. 在您使用的 VM 中,它错误地无法链接签名多态方法。我也可以使用OpenJDK 64-Bit Server VM (build 13-internal+0-adhoc.Jorn.jdk, mixed mode, sharing)最新的 OpenJDK 源代码来重现它。

  2. 其中为描述符javac发出错误的返回类型。

我假设规范是主要权威,但在这种情况下,规范/文档似乎更有可能在实施之后没有更新,这就是 eclipsec 的问题。


我收到了我发给 jdk-dev的电子邮件的回复,由 Dan Smith 回复。

对于 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

于 2019-01-05T18:30:54.260 回答