invokedynamic
指令用于帮助虚拟机在运行时确定方法引用,而不是在编译时对其进行硬连接。
这对于动态语言很有用,在动态语言中,直到运行时才知道确切的方法和参数类型。但 Java lambda 的情况并非如此。它们被转换为具有明确定义的参数的静态方法。并且可以使用 invokestatic
调用此方法。
那么 lambda 需要 invokedynamic
吗,尤其是在性能受到影响的情况下?
最佳答案
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 都必须成为某个基类或接口(interface)的实例。该实例有时包含从原始方法捕获的变量的副本,有时包含指向父对象的指针。 这可以作为匿名类实现。
为什么调用动态
简短的答案是:在运行时生成代码。
Java 维护者选择在运行时生成实现类。
这是通过调用 java.lang.invoke.LambdaMetafactory.metafactory 来完成的。
由于该调用的参数(返回类型、接口(interface)和捕获的参数)可能会更改,因此需要 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这在性能上相当于手动编写匿名实例化。
关于java - 为什么使用 invokedynamic 调用 Java 8 lambda?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41470740/