【问题标题】:java.lang.NoSuchMethodError: VarHandle.compareAndSet(VariableHandlesExample,State,State)voidjava.lang.NoSuchMethodError: VarHandle.compareAndSet(VariableHandlesExample,State,State)void
【发布时间】:2019-03-28 12:16:37
【问题描述】:

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

【问题讨论】:

  • 嗨,stack sn-ps 不运行 Java。请编辑以将其列为代码而不是 sn-p。
  • 你是如何执行这段代码的?它是在 Java9+ 上执行的吗? VarHandle.compareAndSet(VarHandleExample,int,int)void 在堆栈跟踪中很奇怪
  • 我在 java 10 上的 eclipse 中执行这段代码
  • @user3168018 因为该方法确实存在。您能否检查您的 Eclipse 版本及其与您正在运行的 JDK 版本的兼容性?看起来主要是配置问题。例如,您可以在使用 eclipse 的代码中使用 var 吗?
  • No var 不起作用。但 VarHandle.getAndAdd(this, 210) 工作正常。

标签: java java-9 methodhandle


【解决方案1】:

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


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 没有必要使用方法引用指定的描述符来声明方法。

在这种情况下,CVarHandle,正在查找的方法是 compareAndSet,描述符是 (LVarHandleExample;II)Z(LVarHandleExample;II)V,具体取决于编译器。

关于Signature Polymorphism的javadoc也很有趣:

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

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

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

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

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

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

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

  2. 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

【讨论】:

  • 我还向 jdk-dev 发送了一封询问此问题的电子邮件,一旦发布就会更新链接。
  • 类似于MethodHandle.invoke[Exact],规范说链接必须成功,但这并不意味着调用会成功,即当类型不匹配时它可能会抛出异常。因此,在实践中,当VarHandle 实现将void 的返回类型视为不匹配时,调用失败是作为链接错误还是作为WrongMethodTypeException 对开发人员来说并不重要。
  • 关于编译器需要做什么,JLS 1.4 参考 API 文档,JLS 2.9.3 明确参考java.lang.invokeVarHandle 说“从源代码的角度来看,这些方法可以接受任何参数,并且它们的多态结果(如果表示)可以转换为任何返回类型。正式地,这是通过给访问模式方法变量arity Object arguments 和 Object 返回类型(如果返回类型是多态的),...”来实现的,这(非常巧妙地)表明返回类型在未声明为时可能不是多态的Object.
  • 嗯,是的,但这不是第一次在 JLS 或 JVMS 中假设特定的编译器行为而没有说明任何内容。在实践中,javac 和 HotSpot 开发人员按照他们的喜好一起工作,当其他人指出不匹配时,规范会进行调整以反映 javac 和 HotSpot 的实际工作。当 HotSpot 急切地使用 default 方法解析接口时发生这种情况(然后成为强制行为)Q&A 并且它发生在匿名内部类实际上不是 finalQ&A
  • 当然,这对任何其他实现者都是不利的,但我记得 Java 5 语言规范是在发布几个月后发布的,显然是为了反映编译器开发人员所做的而不是预期的,所以因为那么,我不再对任何事情感到惊讶了……
猜你喜欢
  • 1970-01-01
  • 2010-09-21
  • 2021-03-09
  • 2023-03-23
  • 2016-01-29
  • 2021-04-22
  • 2019-10-10
  • 2019-01-16
  • 1970-01-01
相关资源
最近更新 更多