invokedynamic指令用于帮助VM在运行时确定方法引用,而不是在编译时硬连接它。
这对于动态语言非常有用,因为在这些语言中,精确的方法和参数类型直到运行时才知道。但Java lambdas的情况并非如此。它们被转换为具有定义良好的参数的静态方法。该方法可以使用invokestatic调用。
那么,invokedynamic对lambda的需求是什么,特别是在性能受到影响的情况下?
发布于 2015-05-02 13:03:27
Lambdas不使用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");
}任何lambda都必须成为某个基类或接口的实例。该实例有时包含从原始方法捕获的变量的副本,有时包含指向父对象的指针。这可以作为一个匿名类实现。
为什么invokedynamic
简单的回答是:在运行时生成代码。
Java维护人员选择在运行时生成实现类。这是通过调用java.lang.invoke.LambdaMetafactory.metafactory完成的。由于该调用的参数(返回类型、接口和捕获的参数)可能会更改,这需要invokedynamic。
使用invokedynamic在运行时构造匿名类,允许JVM在运行时生成该类字节码。对同一语句的后续调用使用缓存版本。使用invokedynamic的另一个原因是将来能够更改实现策略,而不必更改已经编译的代码。
这条路没有走
另一个选项是编译器为每个lambda实例化创建一个innerclass,相当于将上述代码转换为:
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替换为性能与手动编写匿名实例化等价的代码。
发布于 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支持实际的函数类型,那么他们也可以在不引起用户代码问题的情况下,为另一个函数类型更改该策略。
发布于 2017-07-18 09:49:50
当前Java 8的lambda实现是一个复合决策:
BootstrapMethods,它将静态方法调用封装到callsite对象(可以缓存以供以后使用)
所以为了回答你的问题,
invokedynamic的当前lambda实现要比单独的内部类方法快一点,因为不需要加载这些内部类文件,而是动态创建内部类byte[] (例如满足函数接口),并缓存以供以后使用。
https://stackoverflow.com/questions/30002380
复制相似问题