为什么使用invokedynamic 调用Java 8 lambda?
invokedynamic
指令用于帮助 VM 在运行时确定方法引用,而不是在编译时对其进行硬接线.
The invokedynamic
instruction is used to help the VM determine the method reference at runtime instead hardwiring it at compile time.
这对于在运行时才知道确切方法和参数类型的动态语言很有用.但 Java lambda 并非如此.它们被转换为具有明确定义的参数的静态方法.并且可以使用 invokestatic
调用此方法.
This is useful with dynamic languages where the exact method and argument types aren't known until runtime. But that isn't the case with Java lambdas. They are translated to a static method with well defined arguments. And this method can be invoked using invokestatic
.
那么 invokedynamic
对 lambda 有什么需求,尤其是在性能受到影响的情况下?
So then what is the need of invokedynamic
for lambdas, especially when there is a performance hit?
推荐答案
Lambda 不是使用 invokedynamic
调用的,它们的对象表示是使用 invokedynamic
创建的,实际调用是一个常规的 invokevirtual
或 invokeinterface
.
Lambdas are not invoked using invokedynamic
, their object representation is created using invokedynamic
, the actual invocation is a regular invokevirtual
or 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 都必须成为某个基类或接口的实例.该实例有时会包含从原始方法捕获的变量的副本,有时还会包含指向父对象的指针.这可以实现为匿名类.
Any lambda has to become an instance of some base class or interface. That instance will sometimes contain a copy of the variables captured from the original method and sometimes a pointer to the parent object. This can be implemented as an anonymous class.
为什么调用dynamic
简短的回答是:在运行时生成代码.
The short answer is: to generate code in runtime.
Java 维护人员选择在运行时生成实现类.这是通过调用 java.lang.invoke.LambdaMetafactory.metafactory
来完成的.由于该调用的参数(返回类型、接口和捕获的参数)可以更改,因此需要 invokedynamic
.
The Java maintainers chose to generate the implementation class in runtime.
This is done by calling java.lang.invoke.LambdaMetafactory.metafactory
.
Since the arguments for that call (return type, interface, and captured parameters) can change, this requires invokedynamic
.
使用 invokedynamic
在运行时构造匿名类,允许 JVM 在运行时生成该类字节码.对同一语句的后续调用使用缓存版本.使用 invokedynamic
的另一个原因是能够在将来更改实现策略,而无需更改已编译的代码.
Using invokedynamic
to construct the anonymous class in runtime, allows the JVM to generate that class bytecode in runtime. The subsequent calls to the same statement use a cached version. The other reason to use invokedynamic
is to be able to change the implementation strategy in the future without having to change already compiled code.
未走的路
另一个选项是编译器为每个 lambda 实例创建一个内部类,相当于将上面的代码翻译成:
The other option would be the compiler creating an innerclass for each lambda instantiation, equivalent to translating the above code into:
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 的语句时,必须加载和初始化该匿名类.
This requires creating classes in compile time and having to load then during runtime. The way jvm works those classes would reside in the same directory as the original class. And the first time you execute the statement that uses that lambda, that anonymous class would have to be loaded and initialized.
关于性能
第一次调用 invokedynamic
将触发匿名类生成.然后将操作码 invokedynamic
替换为 code 这在性能上等同于手动编写匿名实例化.
The first call to invokedynamic
will trigger the anonymous class generation. Then the opcode invokedynamic
is replaced with code that's equivalent in performance to the writing manually the anonymous instantiation.
相关文章