invokedynamic指令用于帮助VM在运行时确定方法引用,而不是在编译时对其进行硬接线。

这对于动态语言非常有用,在动态语言中直到运行时才知道确切的方法和参数类型。但是Java lambda并非如此。它们被转换为带有定义明确的参数的静态方法。并且可以使用invokestatic调用此方法。

那么invokedynamic对lambda的需求是什么,尤其是在性能受到影响时?

最佳答案

Lambda不能使用invokedynamic调用,它们的对象表示是使用invokedynamic创建的,实际的调用是常规的invokevirtualinvokeinterface

例如:

// 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,其性能与手动编写匿名实例化等效。

09-16 20:26