3

TL;博士

请提供一段用一些众所周知的动态语言(例如 JavaScript)编写的代码,以及该代码在使用 invokedynamic 的 Java 字节码中的样子,并解释为什么使用 invokedynamic 是向前迈出的一步。

背景

我已经用谷歌搜索并阅读了很多关于不再是新的调用动态指令的信息,互联网上的每个人都同意它有助于加速 JVM 上的动态语言。感谢 stackoverflow,我设法让我自己的字节码指令与 Sable/Jasmin 一起运行。

我已经了解 invokedynamic 对于惰性常量很有用,我还认为我了解OpenJDK 如何利用 invokedynamic 进行 lambdas

Oracle有一个小例子,但据我所知,在这种情况下使用invokedynamic会破坏目的,因为“加法器”的例子可能更简单、更快,并且效果与以下字节码表达的效果大致相同:

aload whereeverAIs
checkcast java/lang/Integer
aload whereeverBIs
checkcast java/lang/Integer
invokestatic IntegerOps/adder(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;

因为由于某种原因,Oracle 的引导方法知道两个参数都是整数。他们甚至“承认”:

[..] 它假定参数 [..] 将是 Integer 对象。如果引导方法的参数(在本例中为 callerClass、dynMethodName 和 dynMethodType)不同,则引导方法需要额外的代码来正确链接 invokedynamic [..]。

好吧,是的,如果没有那个有趣的“附加代码”,那么在这里使用 invokedynamic 是没有意义的,是吗?

因此,在那之后以及一些进一步的 Javadoc 和博客条目之后,我认为我已经很好地掌握了如何使用 invokedynamic 作为一个糟糕的替代品,而 invokestatic/invokevirtual/invokevirtual 或 getfield 也可以正常工作。

现在我很好奇如何将invokedynamic指令实际应用到现实世界的用例中,以便它实际上是对“传统”调用的一些改进(除了惰性常量,我得到了那些......)。

4

1 回答 1

8

invokedynamic实际上,如果广义地理解“惰性创建”一词,惰性操作是其主要优势。例如,Java 8 的 lambda 创建特性是一种惰性创建,其中包括包含将由指令最终调用的代码的实际类invokedynamic在执行该指令之前甚至不存在的可能性。

这可以投射到所有类型的脚本语言,以不同于 Java 字节码的形式提供代码(甚至可能在源代码中)。在这里,代码可以在第一次调用方法之前编译并在之后保持链接。但是,如果脚本语言支持重新定义方法,它甚至可能会变得未链接。这使用 , 的第二个重要特性invokedynamic来允许可变CallSite的 s 可以在之后更改,同时在不重新定义的情况下频繁调用时支持最大性能。

这种事后更改invokedynamic目标的可能性允许另一个选项,在第一次调用时链接到解释执行,计算执行次数并仅在超过阈值后编译代码(然后重新链接到编译代码)。


关于基于运行时实例的动态方法分派,显然invokedynamic不能省略分派算法。但是,如果您在运行时检测到特定调用站点将始终调用相同具体类型的方法,您可以将其重新链接CallSite到优化代码,该代码将简短检查目标是否是预期类型并执行优化操作然后但是只有在测试失败时才会分支到执行完整动态调度的通用代码。如果它检测到快速路径检查失败了一定次数,则该实现甚至可以取消优化这样的呼叫站点。

invokevirtual这与 JVM 内部的优化方式和优化方式很接近,invokeinterface因为对于这些,大多数指令在相同的具体类型上调用也是如此。因此,invokedynamic您可以将相同的技术用于任意查找算法。


但是,如果您想要一个完全不同的用例,您可以使用它invokedynamic来实现friend标准访问修饰符规则不支持的语义。假设你有一个类AB它意味着有这样的friend关系,A允许private调用B. 然后,所有这些调用都可以编码为invokedynamic具有所需名称和签名的指令,并指向可能如下所示的public引导方法:B

public static CallSite bootStrap(Lookup l, String name, MethodType type)
    throws NoSuchMethodException, IllegalAccessException {
    if(l.lookupClass()!=A.class || (l.lookupModes()&0xf)!=0xf)
      throw new SecurityException("unprivileged caller");
    l=MethodHandles.lookup();
    return new ConstantCallSite(l.findStatic(B.class, name, type));
}

它首先验证提供的Lookup对象是否具有完全访问权限,A因为只有A能够构造这样的对象。因此,错误来电者的偷偷摸摸的尝试在这个地方被整理出来。然后它使用Lookup具有完全访问权限的对象B来完成链接。因此,这些invokedynamic指令中的每一个都永久链接到第一次调用之后的匹配private方法,B以与之后的普通调用相同的速度运行。

于 2014-12-12T19:48:38.157 回答