invokedynamic 的结果是如何存储的?

How is stored the result of the invokedynamic?

Java 8 引入了对 first-class 函数的支持,它允许将函数分配给变量。在这种情况下,变量必须是函数类型,它由函数接口(只有一个抽象方法的接口)定义。

因此,考虑具有以下定义的接口 I 和 class A 的示例:

interface I{ int foo(); }
class A implements I{ 
  public int foo(){return 7;} 
  public static int bar(){return 11;}
}

我们可以将 I 类型的变量分配给 A 的实例,或者将 方法引用 分配给 bar 的方法 A。两者都可以存储在I类型的变量上,例如:

I i1 = new A(); 
I i2 = A::bar;

如果我们分析之前代码编译产生的字节码,我们将得到:

0: new           #2                  // class A
3: dup
4: invokespecial #3                  // Method A."<init>":()V
7: astore_1
8: invokedynamic #4,  0              // InvokeDynamic #0:foo:()LI;
13: astore_2

对于i1 = new A();,显然相应的指令7: astore_1正在存储与I兼容的A实例。但是,作为 i2 = A::bar 的结果,我们正在存储 8: invokedynamic #4, 0.

的结果

因此,这意味着 invokedynamic 的结果始终是 目标类型 的实例,这是我们分配的变量类型方法参考?

每个 invokedynamic 字节码都引用一个相应的 CONSTANT_InvokeDynamic_info structure in the constant pool. This structure contains a Method Descriptor,用于派生此 invokedynamic 指令的参数类型和 return 值的类型。

在您的示例中,方法描述符是 ()LI; 在源代码到字节码转换期间计算的。

8: invokedynamic #4,  0              // InvokeDynamic #0:foo:()LI;
                                                             ^^^^^

这意味着这个特定的字节码不需要任何参数并且总是产生 I 类型的结果。

invokedynamic 指令的结果,Java 8 的 lambda 表达式和方法引用使用它的方式,确实是目标函数 interface.[=26= 的一个实例]

JVM 记住的不是 invokedynamic 指令的结果,而是 bootstrap 方法 return 编辑的 CallSite 的结果,在新 Java 8 的案例具有 LambdaMetafactory.

的两种方法之一

链接到 invokedynamic 指令的 CallSite 实例封装了 行为 ,而不是特定的结果值。 LambdaMetafactory 提供的实际行为有意未指定以提供广泛的自由度,但当前的实现表现出两种不同的行为。

对于非捕获 lambda 表达式,行为将是 return 在 invokedynamic bootstrapping 期间创建的单个实例。这可以通过创建 constant wrapping MethodHandle wrapped in a ConstantCallSite 来完成。在这种情况下,invokedynamic 指令的后续执行将评估此实例。

对于捕获值的 lambda 表达式,指令将链接到接受捕获值的生成的 class 的构造函数或工厂方法。因此,invokedynamic 指令的后续执行将表现得像一个普通的对象构造(它创建一个 class 的新实例,每次都实现目标 interface)。