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

时间:2011-07-10 02:15:53

标签: java reflection invokedynamic

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

4 个答案:

答案 0 :(得分:151)

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

答案 1 :(得分:8)

前段时间,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

答案 2 :(得分:7)

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

Indy简介

调用动态(也称为 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代码,用于准备实际的调用逻辑:

enter image description here

然后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

在其引导方法表中:

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方法称为bootstrap,它位于java.lang.runtime.ObjectMethods类中。如您所见,此引导程序方法需要以下参数:

  • 表示查询上下文的MethodHandles.Lookup实例 (Ljava/lang/invoke/MethodHandles$Lookup部分)。
  • 引导程序的方法名称(即toStringequalshashCode等) 将要链接。例如,当值为toString时,引导程序 将返回一个ConstantCallSite(一个不变的CallSite), 指向为此特定的实际toString实现 记录。
  • 方法{TypeDescriptor的{​​{1}} 部分)。
  • 一种类型标记,即Ljava/lang/invoke/TypeDescriptor,代表Record类的类型。它的 Class<?>
  • 所有组件名称(即Class<Range>)之间用分号分隔的列表。
  • 每个组件一个min;max。这样,引导方法可以 为此组件创建一个MethodHandle 方法实现。

MethodHandle指令将所有这些参数传递给bootstrap方法。引导方法又返回invokedynamic的实例。 ConstantCallSite保留了对请求的方法实现的引用,例如ConstantCallSite

为什么要印地?

与反射API相比,toString API的效率很高,因为JVM可以完全查看所有调用。因此,只要我们尽可能避免慢路径,JVM可以应用各种优化!

除了效率论点之外,由于its simplicityjava.lang.invoke方法更可靠,更不易碎。

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

最后,让我们假设Java的新版本包含新的且更有效的bootstrap方法实现。借助invokedynamic,我们的应用无需重新编译即可利用此改进。这样,我们就有了某种 Forward Binary Compatibility 。另外,这就是我们正在谈论的动态策略!

其他示例

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

答案 3 :(得分:2)

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

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引入的。