invokedynamic何时有用(除了常量)?
TL; DR
请提供一段用一些众所周知的动态语言(例如JavaScript)编写的代码,以及使用invokedynamic代码如何在Java字节码中看起来像,并解释为什么invokedynamic的用法是这里的一个步骤。
背景
我搜索了很多关于这个不再新的invokedynamic指令,网络上的每个人都同意这个指令,它将有助于加速JVM上的动态语言。 感谢stackoverflow我设法得到我自己的字节码指令与Sable / Jasmin运行。
我已经理解invokedynamic对于懒惰常量是有用的,我也认为我理解OpenJDK如何利用lambda的invokedynamic。
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和Blog条目中,我认为当invokestatic / invokevirtual / invokevirtual或getfield也可以正常工作时,我对如何将invokedynamic用作较差的替代品有了很好的把握。
现在我很好奇如何真正将invokedynamic指令应用于真实世界的用例,以便实际上对“传统”调用的可能性进行了一些改进(除了常量,我得到了那些...)。
实际上,如果广泛地使用术语“懒惰创作”,惰性操作是invokedynamic
的主要优势。 例如,Java 8的lambda创建功能是一种懒惰的创建,它包含了在执行该指令之前,包含将由invokedynamic
指令最终调用的代码的实际类甚至不存在的可能性。
这可以投射到各种以不同于Java字节码的形式提供代码的脚本语言(甚至可能在源代码中)。 在这里,代码可以在第一次调用方法之前编译并在之后保持链接。 但是如果脚本语言支持重新定义方法,它甚至可能变得没有关系。 这使用了invokedynamic
的第二个重要特性,允许可变的CallSite
,它可以在以后更改,同时在不重新定义的情况下频繁调用时支持最高性能。
此后更改invokedynamic
目标的可能性允许另一个选项,链接到第一个调用的解释执行,计算执行次数并仅在超过阈值(然后重新链接到编译后的代码)后编译代码。
关于基于运行时实例的动态方法分派,显然invokedynamic
不能忽略分派算法。 但是如果你在运行时检测到一个特定的调用站点总是调用相同具体类型的方法,你可以将CallSite
重新链接到一个优化的代码,如果目标是预期的类型并执行优化的操作,但只有在该测试失败的情况下才会分支到执行完整动态分派的通用代码。 如果检测到快速路径检查失败一定次数,则实现甚至可以对这样的呼叫站点进行优化。
这接近于如何invokevirtual
和invokeinterface
是在JVM内部优化,这些它也是大多数的这些指令被称为在同一个具体类型的情况。 因此,对于invokedynamic
您可以对任意查找算法使用相同的技术。
但是如果你想要一个完全不同的用例,你可以使用invokedynamic
来实现标准访问修饰符规则不支持的friend
语义。 假设你有一个A
和B
类,它们之间有着这样的friend
关系, A
可以调用B
private
方法。 然后,所有这些调用都可以编码为具有所需名称和签名的invokedynamic
指令,并指向B
的public
引导方法,如下所示:
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
能够构造这样的对象。 因此,在这个地方挑选错误的来电者的偷偷摸摸的尝试。 然后它使用具有对B
完全访问权的Lookup
对象来完成链接。 因此,在第一次调用之后,这些invokedynamic
指令中的每一个都会与B
的匹配private
方法永久链接,并以与普通调用相同的速度运行。
上一篇: When is invokedynamic actually useful (besides lazy constants)?