inDy(invokedynamic)是 java 7 引入的一条新的虚拟机指令,这是自 1.0 以来第一次引入新的虚拟机指令。到了 java 8 这条指令才第一次在 java 应用,用在 lambda 表达式中。 indy 与其他 invoke 指令不同的是它允许由应用级的代码来决定方法解析。所谓应用级的代码其实是一个方法,在这里这个方法被称为引导方法(Bootstrap Method),简称 BSM。BSM 返回一个 CallSite(调用点) 对象,这个对象就和 inDy 链接在一起了。以后再执行这条 inDy 指令都不会创建新的 CallSite 对象。CallSite 就是一个 MethodHandle(方法句柄)的 holder。方法句柄指向一个调用点真正执行的方法。

我对上面内容的解释,就是通过引导方法,创建相关的 methodHandle 执行环境,并组成 CallSite。然后等待字节码层面上的调用,也就是 inDy。

MethodHandle

一个 java 方法的实体有四个构成:

  • 方法名
  • 签名 — 参数列表和返回值
  • 定义方法的类
  • 方法体(代码)
Object rcvr = "a";
try {
    MethodType mt = MethodType.methodType(int.class); // 方法签名
    MethodHandles.Lookup l = MethodHandles.lookup(); // 调用者,也就是当前类。调用者决定有没有权限能访问到方法
    MethodHandle mh = l.findVirtual(rcvr.getClass(), "hashCode", mt); //分别是定义方法的类,方法名,签名
 
    int ret;
    try {
        ret = (int)mh.invoke(rcvr); // 代码,第一个参数就是接收者, **理解上这一步就是 inDy**
        System.out.println(ret);
    } catch (Throwable t) {
        t.printStackTrace();
    }
} catch (IllegalArgumentException | NoSuchMethodException | SecurityException e) {
    e.printStackTrace();
} catch (IllegalAccessException x) {
    x.printStackTrace();
}

Lambda 表达式 和 inDy

这里的主要方法和上文大同小异,但是核心是需要 1、创建匿名类并导入到 JVM 中。2、创建 CallSite

接下来就是执行 LambdaMetafactory.metafactory 方法了,它会创建一个匿名类,这个类是通过 ASM 编织字节码在内存中生成的,然后直接通过 unsafe 直接加载而不会写到文件里。不过可以通过下面的虚拟机参数让它运行的时候输出到文件

-Djdk.internal.lambda.dumpProxyClasses=<path>

这个类是根据 lambda 的特点生成的,输出后可以看到,在这个例子中是这样的:

import java.lang.invoke.LambdaForm.Hidden;
 
// $FF: synthetic class
final class LambdaTest$$Lambda$1 implements Runnable {
    private final String[] arg$1;
 
    private LambdaTest$$Lambda$1(String[] var1) {
        this.arg$1 = var1;
    }
 
    private static Runnable get$Lambda(String[] var0) {
        return new LambdaTest$$Lambda$1(var0);
    }
 
    @Hidden
    public void run() {
        LambdaTest.lambda$main$0(this.arg$1);
    }
}

然后就是创建一个 CallSite,绑定一个 MethodHandle,指向的方法其实就是生成的类中的静态方法 LambdaTest$$Lambda$1.get$Lambda(String[])Runnable。然后把调用点对象返回,到这里 BSM 方法执行完毕。