什么是 invokedynamic 以及如何使用它?

新手上路,请多包涵

我一直听说正在添加到 JVM 中的所有新的很酷的特性,其中一个很酷的特性是 invokedynamic。我想知道它是什么以及它如何使 Java 中的反射编程更容易或更好?

原文由 David K. 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 616
2 个回答

它是一个新的 JVM 指令,它允许编译器生成调用方法的代码,该代码使用比以前可能的规范更宽松的规范——如果您知道什么是“ duck typing ”,invokedynamic 基本上允许 duck typing。作为 Java 程序员,您可以做的事情并不多;不过,如果您是工具创建者,则可以使用它来构建更灵活、更高效的基于 JVM 的语言。 是一篇非常贴心的博文,提供了很多细节。

原文由 Ernest Friedman-Hill 发布,翻译遵循 CC BY-SA 3.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 记录

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 部分)。
  • 方法名称(即 toStringequalshashCode 等)引导程序将链接。 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 之外, 动态调用 还用于实现以下功能:

原文由 Ali Dehghani 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题