该invokedynamic
指令用于帮助VM在运行时确定方法引用,而不是在编译时对其进行硬接线。
这对于动态语言非常有用,在动态语言中直到运行时才知道确切的方法和参数类型。但是Java lambda并非如此。它们被转换为带有定义明确的参数的静态方法。并且可以使用调用此方法invokestatic
。
那么,invokedynamic
对于lambda的需求是什么,尤其是在性能受到影响时?
该invokedynamic
指令用于帮助VM在运行时确定方法引用,而不是在编译时对其进行硬接线。
这对于动态语言非常有用,在动态语言中直到运行时才知道确切的方法和参数类型。但是Java lambda并非如此。它们被转换为带有定义明确的参数的静态方法。并且可以使用调用此方法invokestatic
。
那么,invokedynamic
对于lambda的需求是什么,尤其是在性能受到影响时?
invokedynamic
是不是用来调用拉姆达,但在给定的调用网站创建和绑定一个MethodHandle以拉姆达的身体。
invokedynamic
它,是因为它比“显而易见的”翻译方案提供了性能改进,而不是性能降低。(我不知道您会在哪里遇到性能下降的想法,但这是不正确的。)
Answers:
Lambda不能使用调用invokedynamic
,它们的对象表示是使用创建的invokedynamic
,实际调用是常规invokevirtual
或invokeinterface
。
例如:
// creates an instance of (a subclass of) Consumer
// with invokedynamic to java.lang.invoke.LambdaMetafactory
something(x -> System.out.println(x));
void something(Consumer<String> consumer) {
// invokeinterface
consumer.accept("hello");
}
任何lambda都必须成为某些基类或接口的实例。该实例有时包含从原始方法捕获的变量的副本,有时还包含指向父对象的指针。可以将其实现为匿名类。
为什么调用动态
简短的答案是:在运行时生成代码。
Java维护者选择在运行时生成实现类。这是通过调用完成的java.lang.invoke.LambdaMetafactory.metafactory
。由于该调用的参数(返回类型,接口和捕获的参数)可以更改,因此需要invokedynamic
。
使用invokedynamic
构建在运行时的匿名类,允许JVM生成运行时类的字节码。随后对同一语句的调用使用缓存的版本。使用的另一个原因invokedynamic
是将来能够更改实现策略,而不必更改已编译的代码。
没走的路
另一个选择是编译器为每个lambda实例创建一个内部类,等效于将上述代码转换为:
something(new Consumer() {
public void accept(x) {
// call to a generated method in the base class
ImplementingClass.this.lambda$1(x);
// or repeating the code (awful as it would require generating accesors):
System.out.println(x);
}
);
这要求在编译时创建类,然后在运行时加载。jvm处理这些类的方式将与原始类位于同一目录中。而且,当您第一次执行使用该Lambda的语句时,必须加载和初始化该匿名类。
关于表现
第一次调用invokedynamic
将触发匿名类的生成。然后,将操作码invokedynamic
替换为性能与手动编写匿名实例化等效的代码。
invokedynamic
将产生更多垃圾……”或重复执行同一invokedynamic
条指令会产生任何垃圾?
java.lang.invoke
框架的初始化引起了很多调用,这些调用与您的lambda表达式完全无关。步过,直到i==1
看到多少次你的断点被触发,然后。
MethodHandle.invokeExact
确实使用Object []作为参数。
invokedynamic
指令的开销混淆。如果在循环之前放置一个lambda表达式并进行检查,您将看到在第一次调用的链接过程中invokedynamic
,循环中的指令将findStatic
恰好调用一次。这也适用于所有其他后续执行的invokedynamic
指令,并且指定在动态呼叫站点完成一个链接之后,它将永远保持链接状态。和方法处理也没有采取Object[]
为参数。
java.lang.invoke
:每个动态调用站点在第一次调用之前最多只能从未链接转换为链接。无法撤消已完成的引导方法调用的影响。并在进一步讨论之前尝试理解Signature多态性……
Brain Goetz在他的一篇论文中解释了lambda翻译策略的原因,不幸的是,现在看来这些论文不可用。幸运的是,我保留了一份副本:
翻译策略
我们可以通过多种方式在字节码中表示lambda表达式,例如内部类,方法句柄,动态代理等。这些方法各有利弊。在选择策略时,有两个相互竞争的目标:通过不承诺特定策略来最大化将来优化的灵活性,以及在类文件表示中提供稳定性。通过使用来自JSR 292的invokedynamic功能,将字节码中的lambda创建的二进制表示与在运行时评估lambda表达式的机制分开,我们可以实现这两个目标。我们没有描述生成用于实现lambda表达式的对象的字节码(例如,调用内部类的构造函数),而是描述了构造lambda的方法,并将实际构造委托给语言运行时。该配方被编码在invokedynamic指令的静态和动态参数列表中。
使用invokedynamic可以使我们将翻译策略的选择推迟到运行时为止。运行时实现可以自由选择动态选择策略来评估lambda表达式。运行时实现的选择隐藏在用于lambda构建的标准化(即,平台规范的一部分)API的后面,以便静态编译器可以发出对此API的调用,并且JRE实现可以选择其首选实现策略。调用动力学机制允许完成此操作,而无需使用这种后期绑定方法可能会带来的性能成本。
当编译器遇到lambda表达式时,它首先将lambda主体降低(降低)到其参数列表和返回类型与lambda表达式的参数列表和返回类型匹配的方法中,并可能带有一些其他参数(用于从词法范围捕获的值,如果有的话)。 )在捕获lambda表达式时,它会生成一个invokedynamic调用站点,该站点在被调用时将返回lambda转换成的功能接口的实例。此调用站点称为给定lambda的lambda工厂。lambda工厂的动态参数是从词法范围捕获的值。lambda工厂的bootstrap方法是Java语言运行时库中的一种标准化方法,称为lambda元工厂。
方法引用与lambda表达式的处理方式相同,不同之处在于,大多数方法引用不需要分解为新方法;我们可以简单地为引用的方法加载一个常量方法句柄,并将其传递给元工厂。
因此,这里的想法似乎是封装翻译策略,而不是通过隐藏那些细节来致力于某种特定的处理方式。将来,当解决类型擦除和缺少值类型的问题得到解决,并且Java可能支持实际的函数类型时,他们也可能会去那里并将其更改为另一种策略,而不会对用户代码造成任何问题。
当前Java 8的lambda实现是一个复合决策:
BootstrapMethods
,它将静态方法调用包装到callsite对象中(可以缓存以备后用)因此,为了回答您的问题,
invokedynamic
比单独的内部类方法要快一点,因为不需要加载这些内部类文件,而是可以动态创建内部类byte [](例如,满足Function接口),并且缓存以备后用。