java - 为什么使用invokedynamic 调用Java 8 lambda?

标签 java lambda jvm java-8 bytecode

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

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

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

最佳答案

Lambda 不是使用 invokedynamic 调用的,它们的对象表示是使用 invokedynamic 创建的,实际调用是常规的 invokevirtual调用接口(interface).

例如:

// 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)的实例。该实例有时会包含从原始方法捕获的变量的副本,有时还会包含指向父对象的指针。 这可以实现为匿名类。

为什么调用dynamic

简短的回答是:在运行时生成代码。

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/30002380/

相关文章:

c# - 使用 Roslyn 检测 lambda 表达式的长度

java - 在 Maven 构建期间针对多个 JVM 或库版本进行测试?

java - 从 InputStream 将 tif 文件编码为 Base64

java - 未找到 DatagramPacket 构造函数

c# - 根据满足的较低级别标准获取顶级实体

jvm - 我很好奇 ldc 在 JVM 中的缩写是什么?

java - 在 domain.xml 中添加 SSL keystore 密码作为别名

java - spring-integration-aws java配置

java - Groovy 泛型失败

c++ - 在 lambda 设置中访问两步声明的成员