invokedynamic
指令用于帮助VM在运行时确定方法引用,而不是在编译时对其进行硬接线。
这对于动态语言非常有用,在动态语言中直到运行时才知道确切的方法和参数类型。但是Java lambda并非如此。它们被转换为带有定义明确的参数的静态方法。并且可以使用invokestatic
调用此方法。
那么invokedynamic
对lambda的需求是什么,尤其是在性能受到影响时?
最佳答案
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
替换为code,其性能与手动编写匿名实例化等效。