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

dav*_*k01 152 java reflection invokedynamic

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

Ern*_*ill 161

它是一个新的JVM指令,它允许编译器生成调用方法的代码,这些代码调用比以前更宽松的方法 - 如果你知道" duck typing "是什么,invokedynamic基本上允许鸭子输入.作为Java程序员可以用它做的事情并不多; 但是,如果您是工具创建者,则可以使用它来构建更灵活,更高效的基于JVM的语言.是一个非常甜蜜的博客文章,提供了很多细节.

  • +1"[非常好的博客文章提供了很多细节"](http://blog.headius.com/2008/09/first-taste-of-invokedynamic.html). (15认同)
  • 在日常的Java编程中,通过`meth.invoke(args)`看到用于动态调用方法的反射并不罕见.那么`invokedynamic`如何适合`meth.invoke`? (3认同)
  • 我提到的博客文章谈到了“MethodHandle”,这实际上是同一种东西,但具有更大的灵活性。但是,这一切的真正力量不在于对 Java 语言的补充,而在于 JVM 本身支持本质上更具动态性的其他语言的能力。 (2认同)
  • 似乎 Java 8 使用“invokedynamic”转换了一些 lambda,这使得它具有高性能(与将它们包装在匿名内部类中相比,这几乎是引入“invokedynamic”之前的唯一选择)。很可能许多基于 JVM 的函数式编程语言都会选择编译为这种语言,而不是非内部类。 (2认同)
  • 只是一个小小的警告,那篇 2008 年的博客文章已经过时了,并没有反映实际的发布状态(2011 年)。 (2认同)

Ali*_*ani 9

作为我的Java Records文章的一部分,我阐述了 Inoke 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调用接口方法、invokespecial调用构造函数super()或私有方法和invokevirtual调用实例方法。

尽管存在差异,但这些调用类型具有一个共同特征:我们无法用我们自己的逻辑来丰富它们。相反,invokedynamic 使我们能够以任何我们想要的方式引导调用过程。然后 JVM 负责直接调用 Bootstrapped 方法。

印地如何运作?

JVM 第一次看到一条invokedynamic指令时,它会调用一个特殊的静态方法,称为Bootstrap Method。bootstrap 方法是我们编写的一段 Java 代码,用于准备实际要调用的逻辑:

在此处输入图片说明

然后 bootstrap 方法返回 的一个实例java.lang.invoke.CallSite。这CallSite包含对实际方法的引用,即MethodHandle

从现在开始,JVM 每次invokedynamic再次看到这条指令时,都会跳过慢路径,直接调用底层的可执行文件。除非发生某些变化,否则 JVM 会继续跳过慢速路径。

示例:Java 14 记录

Java 14Records提供了一种很好的紧凑语法来声明应该是哑数据持有者的类。

考虑这个简单的记录:

public record Range(int min, int max) {}
Run Code Online (Sandbox Code Playgroud)

此示例的字节码类似于:

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
Run Code Online (Sandbox Code Playgroud)

在其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
Run Code Online (Sandbox Code Playgroud)

所以调用 Records的bootstrap方法,bootstrap它驻留在java.lang.runtime.ObjectMethods类中。如您所见,此引导方法需要以下参数:

  • MethodHandles.Lookup表示查找上下文(Ljava/lang/invoke/MethodHandles$Lookup部分)的实例。
  • 引导程序要链接的方法名称(即toStringequalshashCode等)。例如,当值为 时toString,bootstrap 将返回一个ConstantCallSiteCallSite永远不会改变的),指向toString这个特定记录的实际实现。
  • 所述TypeDescriptor的方法(Ljava/lang/invoke/TypeDescriptor 部分)。
  • 一个类型标记,即Class<?>表示 Record 类的类型。这是 Class<Range>在这种情况下。
  • 所有组件名称的分号分隔列表,即min;max.
  • MethodHandle每个组件一个。这样,bootstrap 方法可以MethodHandle基于此特定方法实现的组件创建一个。

invokedynamic指令将所有这些参数传递给 bootstrap 方法。反过来,Bootstrap 方法返回 的一个实例ConstantCallSite。这ConstantCallSite是对请求的方法实现的引用,例如toString

为什么是印地?

与反射 API 相比,该java.lang.invokeAPI 非常高效,因为 JVM 可以完全查看所有调用。因此,JVM 可能会应用各种优化,只要我们尽可能避免慢速路径!

除了效率论点之外,该invokedynamic方法由于其简单性而更加可靠且不易损坏。

此外,为 Java Records 生成的字节码与属性的数量无关。因此,更少的字节码和更快的启动时间。

最后,让我们假设一个新版本的 Java 包括一个新的和更有效的引导方法实现。使用invokedynamic,我们的应用程序无需重新编译即可利用这一改进。这样我们就有了某种前向二进制兼容性。另外,这就是我们所说的动态策略!

其他例子

除了 Java 记录之外,动态调用还用于实现以下功能:


irr*_*ble 8

前段时间,C#在C#中添加了一个很酷的功能,动态语法

Object obj = ...; // no static type available 
dynamic duck = obj;
duck.quack(); // or any method. no compiler checking.
Run Code Online (Sandbox Code Playgroud)

将其视为反射方法调用的语法糖.它可以有非常有趣的应用程序.请参阅http://www.infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter

负责C#动态类型的Neal Gafter刚刚从SUN叛逃到MS.因此,认为在SUN内部讨论过相同的事情并不是不合理的.

我记得不久之后,一些Java家伙宣布了类似的东西

InvokeDynamic duck = obj;
duck.quack(); 
Run Code Online (Sandbox Code Playgroud)

不幸的是,该功能无法在Java 7中找到.非常失望.对于Java程序员来说,他们没有简单的方法来利用invokedynamic他们的程序.

  • `invokedynamic`从未*打算*用于Java程序员.IMO根本不符合Java哲学.它被添加为非Java语言的JVM功能. (40认同)
  • @M Platvoet:我没有及时了解封口,但肯定不是封口的绝对要求.他们讨论的另一个选择就是为匿名内部类创建闭包语法糖,这可以在没有VM规范更改的情况下完成.但我的观点是JSR从来没有打算为Java语言带来动态类型,如果你阅读JSR就很清楚了. (5认同)
  • @Mark从不打算由谁?它不像Java语言名人中有明确的权力结构,或者有明确的集体"意图".至于语言哲学 - 这是非常可行的,请参阅Neal Gafter(叛徒!)解释:http://www.infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter (4认同)
  • @mark peters:invokedynamic实际上也是供java程序员使用,不能直接访问.它是Java 8闭包的基础. (3认同)
  • @irreputable:JSR贡献者从未打算过。这说明JSR的名称是“在Java平台上支持动态类型的语言”。Java不是动态类型的语言。 (2认同)

小智 6

在继续调用动态之前,有两个概念需要理解。

1. 静态与动态类型

静态- 在编译时执行类型检查(例如 Java)

动态- 在运行时执行类型检查(例如 JavaScript)

类型检查是验证程序类型安全的过程,即检查类和实例变量、方法参数、返回值和其他变量的类型信息。例如,Java 在编译时知道 int、String、..,而 JavaScript 中的对象类型只能在运行时确定

2. 强打字与弱打字

- 指定对其操作(例如 Java)提供的值类型的限制

- 如果操作的参数具有不兼容的类型(例如 Visual Basic),则转换(强制转换)这些参数

知道 Java 是静态和弱类型的,如何在 JVM 上实现动态和强类型的语言?

invokedynamic 实现了一个运行时系统,该系统可以在程序编译后选择最合适的方法或函数实现。

示例: 具有 (a + b) 并且在编译时对变量 a,b 一无所知,invokedynamic 会在运行时将此操作映射到 Java 中最合适的方法。例如,如果结果 a,b 是字符串,则调用 method(String a, String b)。如果结果 a,b 是整数,则调用 method(int a, int b)。

invokedynamic 是在 Java 7 中引入的。