Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/375.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
为什么使用invokedynamic调用Java 8 lambda?_Java_Lambda_Jvm_Java 8_Bytecode - Fatal编程技术网

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

为什么使用invokedynamic调用Java 8 lambda?,java,lambda,jvm,java-8,bytecode,Java,Lambda,Jvm,Java 8,Bytecode,invokedynamic指令用于帮助VM在运行时确定方法引用,而不是在编译时对其进行硬连接 这对于动态语言非常有用,因为在动态语言中,确切的方法和参数类型直到运行时才知道。但Java lambdas并非如此。它们被转换为具有定义良好的参数的静态方法。可以使用invokestatic调用此方法 那么,对于lambda,invokedynamic有什么需要,特别是在性能受到影响时?lambda不是使用invokedynamic调用的,它们的对象表示是使用invokedynamic创建的,实际调用是

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

这对于动态语言非常有用,因为在动态语言中,确切的方法和参数类型直到运行时才知道。但Java lambdas并非如此。它们被转换为具有定义良好的参数的静态方法。可以使用
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"); 
}
这需要在编译时创建类,然后在运行时加载。jvm运行这些类的方式将与原始类驻留在同一目录中。第一次执行使用该lambda的语句时,必须加载并初始化该匿名类

关于性能的信息


invokedynamic
的第一次调用将触发匿名类生成。然后,将操作码
invokedynamic
替换为性能相当于手动编写匿名实例化的操作码。

Brain Goetz解释了lambda翻译策略的原因,不幸的是,其中一种翻译策略现在似乎不可用。幸运的是,我保留了一份副本:

翻译策略

我们可以通过多种方式在中表示lambda表达式 字节码,例如内部类、方法句柄、动态代理和 其他的。这些方法各有利弊。在选择 战略,有两个相互竞争的目标:最大限度地提高 通过不致力于特定战略的未来优化,与 提供类文件表示的稳定性。我们能够实现 这两个目标都是通过使用JSR292中的InvokedDynamic特性实现的 在字节码中分离lambda创建的二进制表示形式 从运行时计算lambda表达式的机制。 而不是生成字节码来创建实现 lambda表达式(例如调用内部 类),我们描述了构造lambda和委托的方法 语言运行库的实际构造。那道菜很好吃 编码在InvokedDynamic的静态和动态参数列表中 指示

使用invokedynamic可以推迟选择翻译 直到运行时的策略。运行时实现可以自由选择 动态计算lambda表达式的策略。运行时 实现选择隐藏在标准化(即 平台规范)API用于lambda构造,以便 静态编译器可以发出对此API和JRE实现的调用 可以选择他们首选的实施策略。被调用的动力学 机械师允许在没有性能成本的情况下完成这项工作 否则,这种后期绑定方法可能会带来额外的影响

当编译器遇到lambda表达式时,它首先降低 (desugars)将lambda体转换为参数列表和 返回类型与lambda表达式的返回类型匹配,可能与 附加参数(用于从词法范围捕获的值,如果需要) 在捕获lambda表达式的点, 它生成一个invokedynamic调用站点,调用该站点时返回 lambda正在连接到的功能接口的实例 转换。该调用站点称为给定对象的lambda工厂 拉姆达。lambda工厂的动态参数是值 从词汇范围捕获。lambda的bootstrap方法 factory是Java语言运行库中的标准化方法, 称为lambda元工厂。静态引导参数捕获 编译时关于lambda的已知信息(函数 接口,该接口是 脱糖lambda body,有关SAM类型是否正确的信息 可序列化等)

方法引用的处理方式与lambda表达式相同, 除了大多数方法引用不需要被分解成 新方法;我们可以简单地为 引用的方法并将其传递给元工厂


因此,这里的想法似乎是封装翻译策略,而不是通过隐藏这些细节来致力于一种特定的方式。将来,当类型擦除和缺少值类型问题得到解决,Java可能支持实际的函数类型时,他们也可以在不导致用户代码出现任何问题的情况下,将该策略更改为另一种策略。

当前Java 8的lambda实现是一个复合决策:

  • 将lambda表达式编译为封闭类中的静态方法;而不是将lambda编译为单独的内部类文件(Scala以这种方式编译,从而生成许多$$$类文件)
  • 引入一个常量池:
    BootstrapMethods
    ,它将静态方法调用包装到callsite对象(可以缓存以供以后使用)
那么回答你的问题,


  • 当前使用
    invokedynamic
    的lambda实现比单独的内部类方式快一点,因为不需要加载这些内部类文件,而是动态创建内部类字节[](以满足例如函数接口),并缓存以供以后使用
  • JVM团队仍然可以选择生成单独的内部类(通过引用封闭类的静态meth)
    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);
        }
    );