首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么使用invokedynamic调用Java 8 lambda?

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

Stack Overflow用户
提问于 2015-05-02 12:35:53
回答 3查看 18.4K关注 0票数 72

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

这对于动态语言非常有用,因为在这些语言中,精确的方法和参数类型直到运行时才知道。但Java lambdas的情况并非如此。它们被转换为具有定义良好的参数的静态方法。该方法可以使用invokestatic调用。

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

EN

回答 3

Stack Overflow用户

发布于 2015-05-02 13:03:27

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

例如:

代码语言:javascript
复制
// 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都必须成为某个基类或接口的实例。该实例有时包含从原始方法捕获的变量的副本,有时包含指向父对象的指针。这可以作为一个匿名类实现。

为什么invokedynamic

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

Java维护人员选择在运行时生成实现类。这是通过调用java.lang.invoke.LambdaMetafactory.metafactory完成的。由于该调用的参数(返回类型、接口和捕获的参数)可能会更改,这需要invokedynamic

使用invokedynamic在运行时构造匿名类,允许JVM在运行时生成该类字节码。对同一语句的后续调用使用缓存版本。使用invokedynamic的另一个原因是将来能够更改实现策略,而不必更改已经编译的代码。

这条路没有走

另一个选项是编译器为每个lambda实例化创建一个innerclass,相当于将上述代码转换为:

代码语言:javascript
复制
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替换为性能与手动编写匿名实例化等价的代码

票数 76
EN

Stack Overflow用户

发布于 2015-05-02 13:20:51

Brain解释了在他的论文中使用lambda翻译策略的原因,但不幸的是,这一策略现在似乎无法实现。幸运的是我保存了一份副本:

翻译策略 我们可以用字节码表示lambda表达式,例如内部类、方法句柄、动态代理等。这些方法各有优缺点。在选择策略时,有两个相互竞争的目标:通过不承诺特定的策略来最大化未来优化的灵活性,以及在类文件表示中提供稳定性。我们可以通过使用JSR 292的invokedynamic特性将字节码中的lambda创建的二进制表示与运行时评估lambda表达式的机制分开来实现这两个目标。我们没有生成字节码来创建实现lambda表达式的对象(比如调用内部类的构造函数),而是描述了构建lambda的方法,并将实际的构造委托给语言运行时。该配方被编码在invokedynamic指令的静态和动态参数列表中。 使用invokedynamic允许我们将翻译策略的选择推迟到运行时。运行时实现可以动态选择策略来评估lambda表达式。运行时实现选择隐藏在用于lambda构造的标准化API (即平台规范的一部分)后面,因此静态编译器可以发出对此API的调用,而JRE实现可以选择它们的首选实现策略。invokedynamic机制允许这样做,而不需要这种延迟绑定方法可能带来的性能成本。 当编译器遇到lambda表达式时,它首先将lambda体降到一个方法中,该方法的参数列表和返回类型与lambda表达式的参数类型相匹配,可能还带有一些附加参数(用于从词法范围捕获的值,如果有的话)。在捕获lambda表达式时,它生成一个invokedynamic站点,当调用该站点时,该站点返回一个函数接口实例,而lambda将被转换到该接口。这个呼叫站点称为为给定的lambda的lambda工厂。指向lambda工厂的动态参数是从词法范围捕获的值。lambda工厂的引导方法是Java语言运行库中的一个标准化方法,称为lambda元库。静态引导参数捕获编译时已知的有关lambda的信息(它将被转换到的函数接口、所设计的lambda主体的方法句柄、关于SAM类型是否可序列化的信息,等等)。 方法引用的处理方式与lambda表达式相同,只不过大多数方法引用不需要设计成新的方法;我们可以简单地为引用的方法加载一个常量的方法句柄,并将其传递给图元库。

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

票数 50
EN

Stack Overflow用户

发布于 2017-07-18 09:49:50

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

    1. 将lambda表达式编译为封闭类中的静态方法;而不是编译lambda以分离内部类文件(Scala以这种方式编译,从而生成许多$$$类文件)

    1. 引入一个常量池:BootstrapMethods,它将静态方法调用封装到callsite对象(可以缓存以供以后使用)

所以为了回答你的问题,

    1. 使用invokedynamic的当前lambda实现要比单独的内部类方法快一点,因为不需要加载这些内部类文件,而是动态创建内部类byte[] (例如满足函数接口),并缓存以供以后使用。

    1. JVM团队仍然可以选择生成单独的内部类(通过引用封闭类的静态方法):它是灵活的

票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/30002380

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档