java 为什么使用invokedynamic调用Java 8 lambdas?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/30002380/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why are Java 8 lambdas invoked using invokedynamic?
提问by Kshitiz Sharma
The invokedynamic
instruction is used to help the VM determine the method reference at runtime instead hardwiring it at compile time.
该invokedynamic
指令用于帮助 VM 在运行时确定方法引用,而不是在编译时对其进行硬连线。
This is useful with dynamic languages where the exact method and argument types aren't known until runtime. But that isn't the case with Java lambdas. They are translated to a static method with well defined arguments. And this method can be invoked using invokestatic
.
这对于动态语言非常有用,因为在运行时才知道确切的方法和参数类型。但 Java lambda 并非如此。它们被转换为具有明确定义的参数的静态方法。并且可以使用invokestatic
.
So then what is the need of invokedynamic
for lambdas, especially when there is a performance hit?
那么invokedynamic
对 lambda 表达式的需求是什么,尤其是在性能受到影响的情况下?
回答by Daniel Sperry
Lambdas are not invoked using invokedynamic
, their object representation is created using invokedynamic
, the actual invocation is a regular invokevirtual
or invokeinterface
.
Lambda 不是使用 调用的invokedynamic
,它们的对象表示是使用创建的invokedynamic
,实际调用是常规的invokevirtual
或invokeinterface
。
For example:
例如:
// 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");
}
Any lambda has to become an instance of some base class or interface. That instance will sometimes contain a copy of the variables captured from the original method and sometimes a pointer to the parent object. This can be implemented as an anonymous class.
任何 lambda 都必须成为某个基类或接口的实例。该实例有时会包含从原始方法捕获的变量的副本,有时会包含指向父对象的指针。这可以实现为匿名类。
Why invokedynamic
为什么调用动态
The short answer is: to generate code in runtime.
简短的回答是:在运行时生成代码。
The Java maintainers chose to generate the implementation class in runtime.
This is done by calling java.lang.invoke.LambdaMetafactory.metafactory
.
Since the arguments for that call (return type, interface, and captured parameters) can change, this requires invokedynamic
.
Java 维护者选择在运行时生成实现类。这是通过调用java.lang.invoke.LambdaMetafactory.metafactory
. 由于该调用的参数(返回类型、接口和捕获的参数)可以更改,因此这需要invokedynamic
.
Using invokedynamic
to construct the anonymous class in runtime, allows the JVM to generate that class bytecode in runtime. The subsequent calls to the same statement use a cached version. The other reason to use invokedynamic
is to be able to change the implementation strategy in the future without having to change already compiled code.
使用invokedynamic
构建在运行时的匿名类,允许JVM生成运行时类的字节码。对同一语句的后续调用使用缓存版本。使用的另一个原因invokedynamic
是能够在将来更改实现策略而不必更改已编译的代码。
The road not taken
未走的路
The other option would be the compiler creating an innerclass for each lambda instantiation, equivalent to translating the above code into:
另一种选择是编译器为每个 lambda 实例化创建一个内部类,相当于将上面的代码翻译成:
something(new Consumer() {
public void accept(x) {
// call to a generated method in the base class
ImplementingClass.this.lambda(x);
// or repeating the code (awful as it would require generating accesors):
System.out.println(x);
}
);
This requires creating classes in compile time and having to load then during runtime. The way jvm works those classes would reside in the same directory as the original class. And the first time you execute the statement that uses that lambda, that anonymous class would have to be loaded and initialized.
这需要在编译时创建类,然后在运行时加载。jvm 的工作方式是,这些类将与原始类位于同一目录中。第一次执行使用该 lambda 的语句时,必须加载和初始化该匿名类。
About performance
关于性能
The first call to invokedynamic
will trigger the anonymous class generation. Then the opcode invokedynamic
is replaced with codethat's equivalent in performance to the writing manually the anonymous instantiation.
第一次调用invokedynamic
将触发匿名类生成。然后操作码invokedynamic
被替换为在性能上与手动编写匿名实例化等效的代码。
回答by Edwin Dalorzo
Brain Goetz explained the reasons for the lambda translation strategy in one of his paperswhich unfortunately now seem unavailable. Fortunately I kept a copy:
Brain Goetz 在他的一篇论文中解释了 lambda 翻译策略的原因,但遗憾的是现在似乎无法获得。幸运的是,我保留了一份副本:
Translation strategy
There are a number of ways we might represent a lambda expression in bytecode, such as inner classes, method handles, dynamic proxies, and others. Each of these approaches has pros and cons. In selecting a strategy, there are two competing goals: maximizing flexibility for future optimization by not committing to a specific strategy, vs providing stability in the classfile representation. We can achieve both of these goals by using the invokedynamic feature from JSR 292 to separate the binary representation of lambda creation in the bytecode from the mechanics of evaluating the lambda expression at runtime. Instead of generating bytecode to create the object that implements the lambda expression (such as calling a constructor for an inner class), we describe a recipe for constructing the lambda, and delegate the actual construction to the language runtime. That recipe is encoded in the static and dynamic argument lists of an invokedynamic instruction.
The use of invokedynamic lets us defer the selection of a translation strategy until run time. The runtime implementation is free to select a strategy dynamically to evaluate the lambda expression. The runtime implementation choice is hidden behind a standardized (i.e., part of the platform specification) API for lambda construction, so that the static compiler can emit calls to this API, and JRE implementations can choose their preferred implementation strategy. The invokedynamic mechanics allow this to be done without the performance costs that this late binding approach might otherwise impose.
When the compiler encounters a lambda expression, it first lowers (desugars) the lambda body into a method whose argument list and return type match that of the lambda expression, possibly with some additional arguments (for values captured from the lexical scope, if any.) At the point at which the lambda expression would be captured, it generates an invokedynamic call site, which, when invoked, returns an instance of the functional interface to which the lambda is being converted. This call site is called the lambda factory for a given lambda. The dynamic arguments to the lambda factory are the values captured from the lexical scope. The bootstrap method of the lambda factory is a standardized method in the Java language runtime library, called the lambda metafactory. The static bootstrap arguments capture information known about the lambda at compile time (the functional interface to which it will be converted, a method handle for the desugared lambda body, information about whether the SAM type is serializable, etc.)
Method references are treated the same way as lambda expressions, except that most method references do not need to be desugared into a new method; we can simply load a constant method handle for the referenced method and pass that to the metafactory.
翻译策略
我们可以通过多种方式在字节码中表示 lambda 表达式,例如内部类、方法句柄、动态代理等。这些方法中的每一种都有优点和缺点。在选择策略时,有两个相互竞争的目标:通过不承诺特定策略来最大化未来优化的灵活性,以及在类文件表示中提供稳定性。我们可以通过使用 JSR 292 中的调用动态特性将字节码中 lambda 创建的二进制表示与在运行时评估 lambda 表达式的机制分开来实现这两个目标。我们没有生成字节码来创建实现 lambda 表达式的对象(例如调用内部类的构造函数),而是描述了构造 lambda 的秘诀,并将实际构造委托给语言运行时。该配方编码在调用动态指令的静态和动态参数列表中。
调用动态的使用让我们可以将翻译策略的选择推迟到运行时。运行时实现可以自由地动态选择策略来评估 lambda 表达式。运行时实现选择隐藏在用于 lambda 构造的标准化(即平台规范的一部分)API 之后,以便静态编译器可以发出对该 API 的调用,并且 JRE 实现可以选择其首选的实现策略。调用动态机制允许在没有这种后期绑定方法可能强加的性能成本的情况下完成此操作。
当编译器遇到 lambda 表达式时,它首先将 lambda 主体降低(脱糖)为一个方法,其参数列表和返回类型与 lambda 表达式的参数列表和返回类型匹配,可能还有一些额外的参数(对于从词法范围捕获的值,如果有的话。 ) 在 lambda 表达式将被捕获的点,它生成一个调用动态调用站点,当调用时,它返回一个函数接口的实例,该 lambda 将被转换为该接口。此调用站点称为给定 lambda 的 lambda 工厂。lambda 工厂的动态参数是从词法作用域中捕获的值。lambda 工厂的 bootstrap 方法是 Java 语言运行库中的标准化方法,称为 lambda 元工厂。
方法引用的处理方式与 lambda 表达式相同,不同之处在于大多数方法引用不需要脱糖为新方法;我们可以简单地为引用的方法加载一个常量方法句柄并将其传递给元工厂。
So, the idea here seemed to be to encapsulate the translation strategy and not commit to a particular way of doing things by hiding those details. In the future when type erasure and lack of value types have been solved and maybe Java supports actual function types, they might just as well go there and change that strategy for another one without causing any problems in the users' code.
因此,这里的想法似乎是封装翻译策略,而不是通过隐藏这些细节来承诺特定的做事方式。将来,当类型擦除和缺少值类型已经解决并且也许 Java 支持实际的函数类型时,他们不妨去那里更改另一种策略,而不会对用户的代码造成任何问题。
回答by fjolt
Current Java 8's lambda implementation is a compound decision:
当前 Java 8 的 lambda 实现是一个复合决策:
- Compile the lambda expression to a static method in the enclosing class; instead of compiling lambdas to separate inner class files (Scala compiles this way, which generates many $$$ class files)
- Introduce a constant pool:
BootstrapMethods
, which wraps the static method invocation to callsite object (can be cached for later use)
- Introduce a constant pool:
- 将 lambda 表达式编译为封闭类中的静态方法;而不是编译 lambdas 来分隔内部类文件(Scala 以这种方式编译,这会生成许多 $$$ 类文件)
- 引入一个常量池:
BootstrapMethods
,将静态方法调用封装到callsite对象中(可以缓存以备后用)
- 引入一个常量池:
So to answer your question,
所以要回答你的问题,
- the current lambda implementation using
invokedynamic
is a little bit faster than the separate inner class way, because no need to load these inner class files, but instead create the inner class byte[] on the fly (to satisfy for example the Function interface), and cached for later use.
- the current lambda implementation using
- JVM team may still choose to generate separate inner class (by referencing the enclosing class's static methods) files: it's flexible
- 当前使用的 lambda 实现
invokedynamic
比单独的内部类方式快一点,因为不需要加载这些内部类文件,而是动态创建内部类 byte[](以满足例如 Function 接口),并且缓存以备后用。
- 当前使用的 lambda 实现
- JVM 团队可能仍然选择生成单独的内部类(通过引用封闭类的静态方法)文件:它很灵活