什么时候invokedynamic实际上有用(除了惰性常量)?

yan*_*kee 1 jvm dynamic invokedynamic

TL; DR

请提供一段用某种众所周知的动态语言(例如JavaScript)编写的代码,以及使用invokedynamic在Java字节码中的代码看起来如何,并在这里解释为什么使用invokedynamic是向前迈进的一步。

背景

我已经在Google上搜索并阅读了很多有关“不再是新的” invokedynamic指令的信息,互联网上的每个人都同意这将有助于加快JVM上的动态语言的速度。多亏了stackoverflow,我设法使自己的字节码指令与Sable / Jasmin一起运行。

我已经知道invokedynamic对于惰性常量很有用,并且我还认为OpenJDK是如何利用lambda的invokedynamic的

Oracle有一个很小的示例,但是据我所知,在这种情况下,invokedynamic的使用无法达到目的,因为“ adder”的示例可以更简单,更快,并且具有与以下字节码大致相同的效果:

aload whereeverAIs
checkcast java/lang/Integer
aload whereeverBIs
checkcast java/lang/Integer
invokestatic IntegerOps/adder(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;
Run Code Online (Sandbox Code Playgroud)

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

[..]假设参数[..]将是Integer对象。如果引导程序方法的参数(在此示例中,callerClass,dynMethodName和dynMethodType)不同,则引导程序方法需要附加代码才能正确链接invokedynamic [..]。

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

因此,在那之后,再加上另外两个Javadoc和Blog条目,我认为我对如何使用invokedynamic进行很好的掌握,当invokestatic / invokevirtual / invokevirtual或getfield也可以正常使用时,如何使用invokedynamic作为不良替代品。

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

Hol*_*ger 5

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

可以将其投影到各种脚本语言中,以与Java字节码不同的形式交付代码(甚至在源代码中也可以)。在这里,代码可以在第一次调用方法之前进行编译,并在之后保持链接状态。但是,如果脚本语言支持方法的重新定义,它甚至可能变得未链接。这使用的第二个重要功能invokedynamic,允许可变的CallSites,之后可以更改,同时在频繁调用而无需重新定义时支持最大性能。

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


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

invokevirtualinvokeinterface在JVM中进行内部优化的方式非常接近,因为在大多数情况下,大多数指令是在相同的具体类型上调用的。因此,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));
}
Run Code Online (Sandbox Code Playgroud)

它首先验证所提供的Lookup对象是否具有完全的访问权限,A因为仅A具有构造此类对象的能力。因此,在这个地方可以解决错误呼叫者的偷偷摸摸的企图。然后,它使用Lookup具有完全访问权限的对象B来完成链接。因此,这些invokedynamic指令中的每一个都永久链接到第一次调用之后的匹配private方法B,其运行速度与之后的普通调用相同。

  • @Eugene 首先,该方法不会向公众公开,因为它只向经过验证的调用者返回一个“CallSite”。其次,“CallSite”根本不包装查找对象;它只封装了一个特定的“MethodHandle”。此外,该方法位于“B”内部,愿意向“A”提供对其自身的访问。我不知道,你有什么选择。当“A”使用“privateLookUpIn”访问“B”时,类“B”根本无法控制它。 (2认同)