什么是invokedynamic?如何使用它?


159

我一直在听到所有添加到JVM的新功能,这些功能之一是invokedynamic。我想知道它是什么,它如何使Java中的反射式编程更容易或更好?

Answers:


165

这是一条新的JVM指令,它允许编译器生成代码,以比以前更宽松的规范来调用方法-如果您知道“ 鸭子类型 ”是什么,invokedynamic基本上允许进行鸭子类型。作为Java程序员,您的工作量并不太多。但是,如果您是工具的创建者,则可以使用它来构建更灵活,更高效的基于JVM的语言。是一篇非常贴切的博客文章,提供了很多细节。


3
在日常的Java编程中,经常会看到反射被用来与一起动态调用方法meth.invoke(args)。那么如何invokedynamic配合meth.invoke呢?
David K.

1
我提到的博客文章谈到MethodHandle,这的确是一样的东西,但具有更大的灵活性。但是,所有这一切的真正力量并不在于Java语言之外,而在于JVM本身支持本质上更具动态性的其他语言的能力。
欧内斯特·弗里德曼·希尔


1
似乎Java 8转换了一些lambda,invokedynamic从而使lambda 表现出色(与将它们包装到匿名内部类中相比,这几乎是引入之前的唯一选择invokedynamic)。JVM之上的许多功能性编程语言很可能会选择编译为该类,而不是内部非类。
2015年

2
只是一个小警告,2008年的博客帖子已经过时了,绝不能反映实际的发布状态(2011年)。
Holger

9

不久前,C#添加了一个很酷的功能,即C#中的动态语法

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

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

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

我记得不久之后,一些Java伙计宣布了类似的内容

InvokeDynamic duck = obj;
duck.quack(); 

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


41
invokedynamic从未打算供Java程序员使用。IMO根本不符合Java哲学。它被添加为非Java语言的JVM功能。
马克·彼得斯

5
@Mark谁没有打算?这不像Java语言名人中有明确的权力结构,或者有明确定义的集体“意图”。至于语言哲学-这是相当可行的,看到尼尔Gafter(叛徒!)解释:infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter
irreputable

3
@mark peters:invokedynamic实际上也只适用于不能直接访问的Java程序员。它是Java 8闭包的基础。
M Platvoet

2
@irreputable:JSR贡献者从未打算过。这说明JSR的名称是“在Java平台上支持动态类型的语言”。Java不是动态类型的语言。
马克·彼得斯

5
@M Platvoet:我没有跟上闭包的最新发展,但是对于闭包绝对不是绝对的要求。他们讨论的另一种选择是简单地为匿名内部类制作闭包语法糖,而无需更改VM规范即可完成。但是我的观点是,JSR从未打算将动态类型带入Java语言,如果您阅读JSR,那就很清楚了。
马克·彼得斯

4

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

1.静态与动态输入

静态的 -编译时进行瓶坯类型检查(例如Java)

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

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

2.强vs.弱打字

-指定对其操作提供的值类型的限制(例如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)。

Java 7中引入了invokedynamic。


4

在我的Java Records文章中,我阐述了Inoke Dynamic背后的动机。让我们从Indy的粗略定义开始。

印地简介

Invoke Dynamic(也称为Indy)是JSR 292的一部分,旨在增强JVM对动态类型语言的支持。在Java 7中首次发布后,invokedynamic操作码及其java.lang.invoke行李箱被基于动态JVM的语言(如JRuby)广泛使用。

尽管indy专为增强动态语言支持而设计,但它提供的功能远不止这些。事实上,适合语言设计人员需要从动态类型的杂技到动态策略的任何形式的动态的地方!

例如,即使Java是静态类型的语言,Java 8 Lambda表达式实际上是使用实现的invokedynamic

用户定义的字节码

一段时间以来,JVM确实支持四种方法调用类型:invokestatic调用静态方法,invokeinterface调用接口方法,invokespecial调用构造函数super()或私有方法以及invokevirtual调用实例方法。

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

Indy如何工作?

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

在此处输入图片说明

然后bootstrap方法返回的实例java.lang.invoke.CallSite。这CallSite是对实际方法的参考,即MethodHandle

从现在开始,每次JVM invokedynamic再次看到该指令时,它都会跳过慢速路径并直接调用基础可执行文件。除非有所更改,否则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

因此,将调用Records 的引导方法,该方法bootstrap位于java.lang.runtime.ObjectMethods类中。如您所见,此引导程序方法需要以下参数:

  • MethodHandles.Lookup表示查找上下文的实例(该Ljava/lang/invoke/MethodHandles$Lookup部分)。
  • 方法名(即toStringequalshashCode等等)引导将要链接。例如,当值为时toString,bootstrap将返回一个ConstantCallSiteCallSite永远不变)的指针,该指针指向toString该特定Record 的实际实现。
  • 所述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记录生成的字节码与属性的数量无关。因此,更少的字节码和更快的启动时间。

最后,让我们假设Java的新版本包含一个新的且更有效的bootstrap方法实现。使用invokedynamic,我们的应用无需重新编译即可利用此改进。这样,我们就具有某种正向二进制兼容性。另外,这就是我们正在讨论的动态策略!

其他例子

除了Java Records,invoke动态还用于实现以下功能:

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.