我一直听说正在添加到 JVM 中的所有新的很酷的特性,其中一个很酷的特性是 invokedynamic。我想知道它是什么以及它如何使 Java 中的反射编程更容易或更好?
原文由 David K. 发布,翻译遵循 CC BY-SA 4.0 许可协议
我一直听说正在添加到 JVM 中的所有新的很酷的特性,其中一个很酷的特性是 invokedynamic。我想知道它是什么以及它如何使 Java 中的反射编程更容易或更好?
原文由 David K. 发布,翻译遵循 CC BY-SA 4.0 许可协议
作为我的 Java Records 文章的一部分,我阐述了 Invoke Dynamic 背后的动机。让我们从 Indy 的粗略定义开始。
Invoke Dynamic (也称为 Indy )是 JSR 292 的一部分,旨在增强 JVM 对动态类型语言的支持。在 Java 7 中首次发布后, invokedynamic
操作码及其 java.lang.invoke
行李箱被基于 JVM 的动态语言(如 JRuby)广泛使用。
尽管 Indy 专门设计用于增强动态语言支持,但它提供的远不止于此。事实上, 它适用于语言设计者需要任何形式的动态性的地方,从动态类型杂技到动态策略!
例如, Java 8 Lambda 表达式实际上是使用 invokedynamic
实现的,尽管 Java 是一种静态类型语言!
很长一段时间,JVM 确实支持四种方法调用类型: invokestatic
调用静态方法, invokeinterface
调用接口方法, super()
invokespecial
调用构造函数 ---
或私有方法和 invokevirtual
调用实例方法。
尽管存在差异,但这些调用类型具有一个共同特征: _我们无法用自己的逻辑来丰富它们_。相反, invokedynamic
使我们能够以我们想要的任何方式引导调用过程。然后 JVM 负责直接调用 Bootstrapped 方法。
JVM 第一次看到 invokedynamic
指令时,它会调用一个名为 Bootstrap Method 的特殊静态方法。 bootstrap 方法是我们编写的一段 Java 代码,用于准备实际要调用的逻辑:
然后 bootstrap 方法返回 java.lang.invoke.CallSite
的一个实例。这 CallSite
包含对实际方法的引用,即 MethodHandle
。
从现在开始,每次 JVM 再次看到这个 invokedynamic
指令时,它都会跳过 Slow Path 并直接调用底层可执行文件。 JVM 会继续跳过慢速路径,除非发生某些变化。
Java 14 Records
提供了一个很好的紧凑语法来声明应该是哑数据持有者的类。
考虑这个简单的记录:
public record Range(int min, int max) {}
这个例子的字节码是这样的:
Compiled from "Range.java"
public java.lang.String toString();
descriptor: ()Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokedynamic #18, 0 // InvokeDynamic #0:toString:(LRange;)Ljava/lang/String;
6: areturn
在其 Bootstrap 方法表 中:
BootstrapMethods:
0: #41 REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:
(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;
Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;
Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
Method arguments:
#8 Range
#48 min;max
#50 REF_getField Range.min:I
#51 REF_getField Range.max:I
因此,记录的 引导 方法称为 bootstrap
位于 java.lang.runtime.ObjectMethods
类中。如您所见,此引导程序方法需要以下参数:
MethodHandles.Lookup
的实例表示查找上下文( Ljava/lang/invoke/MethodHandles$Lookup
部分)。toString
, equals
, hashCode
等)引导程序将链接。 For example, when the value is toString
, bootstrap will return a ConstantCallSite
(a CallSite
that never changes) that points to the actual toString
此特定记录的实现。TypeDescriptor
方法( Ljava/lang/invoke/TypeDescriptor
部分)。Class<?>
,表示 Record 类类型。在这种情况下是 Class<Range>
。min;max
。MethodHandle
。这样,引导程序方法可以基于此特定方法实现的组件创建一个 MethodHandle
。invokedynamic
指令将所有这些参数传递给引导程序方法。 Bootstrap 方法依次返回 ConstantCallSite
的一个实例。这个 ConstantCallSite
持有对请求方法实现的引用,例如 toString
。
与反射 API 不同, java.lang.invoke
API 非常高效,因为 JVM 可以完全看穿所有调用。因此,只要我们尽可能避免慢路径,JVM 可能会应用各种优化!
除了效率论证之外, invokedynamic
方法由于 其简单性 而更加可靠且不那么脆弱。
此外,为 Java Records 生成的字节码与属性的数量无关。因此,更少的字节码和更快的启动时间。
最后,让我们假设一个新版本的 Java 包含一个新的和更有效的引导方法实现。借助 invokedynamic
,我们的应用无需重新编译即可利用这一改进。这样我们就有了某种 Forward Binary Compatibility 。另外,这就是我们正在谈论的动态策略!
除了 Java Records 之外, 动态调用 还用于实现以下功能:
LambdaMetafactory
StringConcatFactory
原文由 Ali Dehghani 发布,翻译遵循 CC BY-SA 4.0 许可协议
15 回答8.4k 阅读
8 回答6.2k 阅读
1 回答4k 阅读✓ 已解决
3 回答6k 阅读
3 回答2.2k 阅读✓ 已解决
2 回答3.1k 阅读
2 回答3.8k 阅读
它是一个新的 JVM 指令,它允许编译器生成调用方法的代码,该代码使用比以前可能的规范更宽松的规范——如果您知道什么是“ duck typing ”,invokedynamic 基本上允许 duck typing。作为 Java 程序员,您可以做的事情并不多;不过,如果您是工具创建者,则可以使用它来构建更灵活、更高效的基于 JVM 的语言。 这 是一篇非常贴心的博文,提供了很多细节。