java基础

invokedynamic指令和Lamada实现原理详解

2022-07-10  本文已影响0人  程序员札记

这个是invokedynamic指令实现的基础,本篇以Lamada的实现原理为例说明invokedynamic的具体用法以及C++字节码解释器中invokedynamic指令的源码实现。

一、Lamada的实现原理
1、字节码分析
想看一个典型的Lamada表达式,具体用法可以参考《java8 Lambda表达式》,代码如下:

package jni;
 
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
 
public class LamadaTest {
 
    public static void main(String[] args) {
        List<String> test= Arrays.asList("a","b","c");
        //完整的写法
//        test.stream().forEach((String s)->{
//            System.out.println("LamadaTest s->"+s);
//        });
        //简写版,lamada自己做类型推断
        Consumer<String> consumer=s->System.out.println("LamadaTest s->"+s);
        test.stream().forEach(consumer);
    }
}

上述代码编译完成后执行javap -v查看具体的字节码指令,去除代码行号表等非关键部分后如下:

Constant pool:
    #1 = Methodref          #18.#43       // java/lang/Object."<init>":()V
    #2 = Class              #44           // java/lang/String
    #3 = String             #45           // a
    #4 = String             #46           // b
    #5 = String             #47           // c
    #6 = Methodref          #48.#49       // java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
    #7 = InvokeDynamic      #0:#55        // #0:accept:()Ljava/util/function/Consumer;
    #8 = InterfaceMethodref #56.#57       // java/util/List.stream:()Ljava/util/stream/Stream;
    #9 = InterfaceMethodref #58.#59       // java/util/stream/Stream.forEach:(Ljava/util/function/Consumer;)V
   #10 = Fieldref           #60.#61       // java/lang/System.out:Ljava/io/PrintStream;
   #11 = Class              #62           // java/lang/StringBuilder
   #12 = Methodref          #11.#43       // java/lang/StringBuilder."<init>":()V
   #13 = String             #63           // LamadaTest s->
   #14 = Methodref          #11.#64       // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #15 = Methodref          #11.#65       // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #16 = Methodref          #66.#67       // java/io/PrintStream.println:(Ljava/lang/String;)V
   #17 = Class              #68           // jni/LamadaTest
   #18 = Class              #69           // java/lang/Object
   #19 = Utf8               <init>
   #20 = Utf8               ()V
   #21 = Utf8               Code
   #22 = Utf8               LineNumberTable
   #23 = Utf8               LocalVariableTable
   #24 = Utf8               this
   #25 = Utf8               Ljni/LamadaTest;
   #26 = Utf8               main
   #27 = Utf8               ([Ljava/lang/String;)V
   #28 = Utf8               args
   #29 = Utf8               [Ljava/lang/String;
   #30 = Utf8               test
   #31 = Utf8               Ljava/util/List;
   #32 = Utf8               consumer
   #33 = Utf8               Ljava/util/function/Consumer;
   #34 = Utf8               LocalVariableTypeTable
   #35 = Utf8               Ljava/util/List<Ljava/lang/String;>;
   #36 = Utf8               Ljava/util/function/Consumer<Ljava/lang/String;>;
   #37 = Utf8               lambda$main$0
   #38 = Utf8               (Ljava/lang/String;)V
   #39 = Utf8               s
   #40 = Utf8               Ljava/lang/String;
   #41 = Utf8               SourceFile
   #42 = Utf8               LamadaTest.java
   #43 = NameAndType        #19:#20       // "<init>":()V
   #44 = Utf8               java/lang/String
   #45 = Utf8               a
   #46 = Utf8               b
   #47 = Utf8               c
   #48 = Class              #70           // java/util/Arrays
   #49 = NameAndType        #71:#72       // asList:([Ljava/lang/Object;)Ljava/util/List;
   #50 = Utf8               BootstrapMethods
   #51 = MethodHandle       #6:#73        // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
   #52 = MethodType         #74           //  (Ljava/lang/Object;)V
   #53 = MethodHandle       #6:#75        // invokestatic jni/LamadaTest.lambda$main$0:(Ljava/lang/String;)V
   #54 = MethodType         #38           //  (Ljava/lang/String;)V
   #55 = NameAndType        #76:#77       // accept:()Ljava/util/function/Consumer;
   #56 = Class              #78           // java/util/List
   #57 = NameAndType        #79:#80       // stream:()Ljava/util/stream/Stream;
   #58 = Class              #81           // java/util/stream/Stream
   #59 = NameAndType        #82:#83       // forEach:(Ljava/util/function/Consumer;)V
   #60 = Class              #84           // java/lang/System
   #61 = NameAndType        #85:#86       // out:Ljava/io/PrintStream;
   #62 = Utf8               java/lang/StringBuilder
   #63 = Utf8               LamadaTest s->
   #64 = NameAndType        #87:#88       // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #65 = NameAndType        #89:#90       // toString:()Ljava/lang/String;
   #66 = Class              #91           // java/io/PrintStream
   #67 = NameAndType        #92:#38       // println:(Ljava/lang/String;)V
   #68 = Utf8               jni/LamadaTest
   #69 = Utf8               java/lang/Object
   #70 = Utf8               java/util/Arrays
   #71 = Utf8               asList
   #72 = Utf8               ([Ljava/lang/Object;)Ljava/util/List;
   #73 = Methodref          #93.#94       // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
   #74 = Utf8               (Ljava/lang/Object;)V
   #75 = Methodref          #17.#95       // jni/LamadaTest.lambda$main$0:(Ljava/lang/String;)V
   #76 = Utf8               accept
   #77 = Utf8               ()Ljava/util/function/Consumer;
   #78 = Utf8               java/util/List
   #79 = Utf8               stream
   #80 = Utf8               ()Ljava/util/stream/Stream;
   #81 = Utf8               java/util/stream/Stream
   #82 = Utf8               forEach
   #83 = Utf8               (Ljava/util/function/Consumer;)V
   #84 = Utf8               java/lang/System
   #85 = Utf8               out
   #86 = Utf8               Ljava/io/PrintStream;
   #87 = Utf8               append
   #88 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
   #89 = Utf8               toString
   #90 = Utf8               ()Ljava/lang/String;
   #91 = Utf8               java/io/PrintStream
   #92 = Utf8               println
   #93 = Class              #96           // java/lang/invoke/LambdaMetafactory
   #94 = NameAndType        #97:#101      // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
   #95 = NameAndType        #37:#38       // lambda$main$0:(Ljava/lang/String;)V
   #96 = Utf8               java/lang/invoke/LambdaMetafactory
   #97 = Utf8               metafactory
   #98 = Class              #103          // java/lang/invoke/MethodHandles$Lookup
   #99 = Utf8               Lookup
  #100 = Utf8               InnerClasses
  #101 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #102 = Class              #104          // java/lang/invoke/MethodHandles
  #103 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #104 = Utf8               java/lang/invoke/MethodHandles
{
  //构造方法
  public jni.LamadaTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
 
  //main方法
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=3, args_size=1
         0: iconst_3   //将int常量3放到操作数栈的顶部
         1: anewarray     #2                  // class java/lang/String,使用操作数栈顶的数组长度和常年池中的数组元素类型java/lang/String构建一个新的数组,创建完成,栈顶是新的数组的引用
         4: dup        //将栈顶的值复制插入到栈顶,即此时操作数栈有两个新数组的引用,指向同一个数组                          
         5: iconst_0   //将int常量0放到操作数栈的顶部
         6: ldc           #3                  // String a  将运行时常年池的数据a放到操作数栈的顶部
         8: aastore  //从操作数栈顶部往下依次读取待插入的引用,插入的位置和插入的数组,将目标索引插入的指定数组的指定位置
         9: dup
        10: iconst_1
        11: ldc           #4                  // String b
        13: aastore  //同上,数组中插入b
        14: dup
        15: iconst_2
        16: ldc           #5                  // String c
        18: aastore   //同上数组中插入c,此时操作数栈顶只有一个数组的引用
        19: invokestatic  #6                  // Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;调用静态方法,方法执行的时候会自动从操作数栈获取方法参数,方法执行完成结果保存在栈顶。#6表示invokestatic要执行的方法Methodref在常量池的索引
        22: astore_1    //将栈顶的引用类型的变量放入本地变量表中索引为1的位置
        23: invokedynamic #7,  0              // InvokeDynamic #0:accept:()Ljava/util/function/Consumer; 执行InvokeDynamic指令,#7表示常年池中的调用点限定符InvokeDynamic,执行完成栈顶放的就是Consumer的实例
        28: astore_2  //将栈顶的引用类型的变量放入本地变量表中索引为2的位置
        29: aload_1   //将本地变量表中索引为1的变量放到栈顶,即List对象
        30: invokeinterface #8,  1            // InterfaceMethod java/util/List.stream:()Ljava/util/stream/Stream; 调用stream方法,执行完成结果放到栈顶
        35: aload_2    //将本地变量表中索引为2的变量放到栈顶,即Consumer对象
        36: invokeinterface #9,  2            // InterfaceMethod java/util/stream/Stream.forEach:(Ljava/util/function/Consumer;)V  调用forEach方法
        41: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      42     0  args   [Ljava/lang/String;
           23      19     1  test   Ljava/util/List;
           29      13     2 consumer   Ljava/util/function/Consumer;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
           23      19     1  test   Ljava/util/List<Ljava/lang/String;>;
           29      13     2 consumer   Ljava/util/function/Consumer<Ljava/lang/String;>;
}
SourceFile: "LamadaTest.java"
//引用的内部类
InnerClasses:
     public static final #99= #98 of #102; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
//InvokeDynamic使用的
BootstrapMethods:
  0: #51 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #52 (Ljava/lang/Object;)V
      #53 invokestatic jni/LamadaTest.lambda$main$0:(Ljava/lang/String;)V
      #54 (Ljava/lang/String;)V

从上诉字节码的分析可知lamada实现的关键就在于invokedynamic指令和后面的调用点限定符了,下面逐一说明。

2、调用点限定符
调用点限定符在运行时常年池中的具体类型是CONSTANT_InvokeDynamic_info,专门为invokedynamic指令定制的,该指令的结构如下:


image.png

各项的具体含义如下:

#7 = InvokeDynamic      #0:#55        // #0:accept:()Ljava/util/function/Consumer; 其中0就是引导方法表的索引
#55 = NameAndType        #76:#77       // accept:()Ljava/util/function/Consumer;
#76 = Utf8               accept
#77 = Utf8               ()Ljava/util/function/Consumer;

3、BootstrapMethods
BootstrapMethods是一种变长属性,位于ClassFile结构的属性表中,用于保存invokedynamic指令使用的引导方法限定符。如果常量池中包含一个或者多个CONSTANT_InvokeDynamic_info类型的常量,则ClassFile结构的属性表必须包含且只能包含一个BootstrapMethods属性。该属性的结构如下:


image.png

各项的说明如下:

bootstrap_methods数组成员的结构说明如下:

引导方发表如下:
BootstrapMethods:
  0: #51 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #52 (Ljava/lang/Object;)V
      #53 invokestatic jni/LamadaTest.lambda$main$0:(Ljava/lang/String;)V
      #54 (Ljava/lang/String;)V 
常量池对应的项如下:
#51 = MethodHandle       #6:#73        // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
#52 = MethodType         #74           //  (Ljava/lang/Object;)V
#53 = MethodHandle       #6:#75        // invokestatic jni/LamadaTest.lambda$main$0:(Ljava/lang/String;)V
#54 = MethodType         #38           //  (Ljava/lang/String;)V

4、CONSTANT_MethodType_info
CONSTANT_MethodType_info是跟java.lang.invoke.MethodType类相对应的,其结构如下:


image.png

各项说明如下:

#52 = MethodType         #74           //  (Ljava/lang/Object;)V
#74 = Utf8               (Ljava/lang/Object;)V
 
#54 = MethodType         #38           //  (Ljava/lang/String;)V
#38 = Utf8               (Ljava/lang/String;)V

5、CONSTANT_MethodHandle_info
CONSTANT_MethodHandle_info跟java.lang.invoke.MethodHandle类相对应的,其结构如下:


image.png

各项说明如下:

#这里第一个6就是 6 (REF_invokeStatic)
#51 = MethodHandle       #6:#73        // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
 
#73 = Methodref          #93.#94       // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
 
#93 = Class              #96           // java/lang/invoke/LambdaMetafactory
#94 = NameAndType        #97:#101      // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
 
 #97 = Utf8               metafactory
 #101 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
由上述分析可知,lamada  invokedynamic的启动方法就是java/lang/invoke/LambdaMetafactory.metafactory方法。

6、LambdaMetafactory.metafactory
metafactory的定义如下:

image.png

对应到上述测试用例,metafactory方法的传参如下:

上述三个参数及启动方法本身的MethodHandle实例的引用都会由JVM自动放入栈中,所以没有在启动方法的参数列表中显示,剩下的三个参数与启动方法的参数一一对应。

CallSite是一个抽象类,不允许开发者继承CallSite实现自定义的子类,它有三个具体的可以实例化的子类,开发者可以继承这三个子类,分别是:

CallSite对应的MethodHandle实例发生改变时要求新的MethodHandle实例与原来的实例的MethodType保持一致,即无论MethodHandle实例怎么改变,CallSite关联的MethodType都保持不变。

CallSite没有定义多余的方法和属性,比较简单,具体如下:

image.png

子类在CallSite基础上主要增加了公开的构造方法,如下:

image.png

8、InnerClassLambdaMetafactory
InnerClassLambdaMetafactory就是实现Lamada的核心,从命名上可知还是使用内部类的形式实现的。构造方法会执行参数的初始化,validateMetafactoryArgs方法完成参数的校验,创建内部类和内部类实例的逻辑都在buildCallSite()方法中,具体如下:

CallSite buildCallSite() throws LambdaConversionException {
        //生成lamada内部类
        final Class<?> innerClass = spinInnerClass();
        //返回的CallSite对应的方法的参数个数为0
        if (invokedType.parameterCount() == 0) {
            final Constructor<?>[] ctrs = AccessController.doPrivileged(
                    new PrivilegedAction<Constructor<?>[]>() {
                @Override
                public Constructor<?>[] run() {
                    //获取构造方法
                    Constructor<?>[] ctrs = innerClass.getDeclaredConstructors();
                    if (ctrs.length == 1) {
                        // The lambda implementing inner class constructor is private, set
                        // it accessible (by us) before creating the constant sole instance
                        ctrs[0].setAccessible(true);
                    }
                    return ctrs;
                }
                    });
            //构造函数有多个抛出异常
            if (ctrs.length != 1) {
                throw new LambdaConversionException("Expected one lambda constructor for "
                        + innerClass.getCanonicalName() + ", got " + ctrs.length);
            }
 
            try {
                //创建一个新的lamada内部类实例
                Object inst = ctrs[0].newInstance();
                //constant方法返回一个特殊的MethodHandle,对其调用invoke方法,永远返回inst实例
                //ConstantCallSite本身是对MethodHandle的包装而已
                return new ConstantCallSite(MethodHandles.constant(samBase, inst));
            }
            catch (ReflectiveOperationException e) {
                throw new LambdaConversionException("Exception instantiating lambda object", e);
            }
        } else {
            try {
                //确保这个lamada内部类已经加载并初始化
                UNSAFE.ensureClassInitialized(innerClass);
                //NAME_FACTORY是lamada内部类返回实例的工厂方法的方法名,具体是get$Lambda
                //invokedType是该方法的方法描述符,具体是()Ljava/util/function/Consumer
                return new ConstantCallSite(
                        MethodHandles.Lookup.IMPL_LOOKUP
                             .findStatic(innerClass, NAME_FACTORY, invokedType));
            }
            catch (ReflectiveOperationException e) {
                throw new LambdaConversionException("Exception finding constructor", e);
            }
        }
    }

spinInnerClass方法就是通过ASM字节码工具生成lamada内部类,具体如下:

private Class<?> spinInnerClass() throws LambdaConversionException {
        String[] interfaces;
        //samBase是想实现的lamada接口类,samIntf表示接口名
        String samIntf = samBase.getName().replace('.', '/');
        boolean accidentallySerializable = !isSerializable && Serializable.class.isAssignableFrom(samBase);
        //markerInterfaces表示需要额外实现的用于标记的接口,比如java.io.Serializable接口,lamada下markerInterfaces为空数组
        if (markerInterfaces.length == 0) {
            //interfaces表示新生成的class需要实现的接口
            interfaces = new String[]{samIntf};
        } else {
            //确保没有重复的接口,所以使用HashSet
            Set<String> itfs = new LinkedHashSet<>(markerInterfaces.length + 1);
            itfs.add(samIntf);
            //将markerInterfaces中的接口加入到 interfaces中
            for (Class<?> markerInterface : markerInterfaces) {
                itfs.add(markerInterface.getName().replace('.', '/'));
                accidentallySerializable |= !isSerializable && Serializable.class.isAssignableFrom(markerInterface);
            }
            interfaces = itfs.toArray(new String[itfs.size()]);
        }
 
        //cw在构造函数中初始化的,用于构造Class的入口
        //添加接口类
        cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC,
                 lambdaClassName, null,
                 JAVA_LANG_OBJECT, interfaces);
 
        //添加字段
        for (int i = 0; i < argDescs.length; i++) {
            FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL,
                                            argNames[i],
                                            argDescs[i],
                                            null, null);
            fv.visitEnd();
        }
        //生成构造函数
        generateConstructor();
        //如果invokedType的参数不止一个,则生成返回lamada实例的工厂方法
        if (invokedType.parameterCount() != 0) {
            generateFactory();
        }
 
        //生成目标方法
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, samMethodName,
                                          samMethodType.toMethodDescriptorString(), null, null);
        //添加方法注解                                  
        mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true);
        new ForwardingMethodGenerator(mv).generate(samMethodType);
 
        //生成用于bridge的方法,lamada下additionalBridges为空数组
        if (additionalBridges != null) {
            for (MethodType mt : additionalBridges) {
                mv = cw.visitMethod(ACC_PUBLIC|ACC_BRIDGE, samMethodName,
                                    mt.toMethodDescriptorString(), null, null);
                mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true);
                new ForwardingMethodGenerator(mv).generate(mt);
            }
        }
 
        //生成序列化方法
        if (isSerializable)
            generateSerializationFriendlyMethods();
        else if (accidentallySerializable)
            generateSerializationHostileMethods();
 
        cw.visitEnd();
 
        // 转换成二进制数组
        final byte[] classBytes = cw.toByteArray();
 
        //如果要求的话将生成的内部类dump出来
        if (dumper != null) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                @Override
                public Void run() {
                    dumper.dumpClass(lambdaClassName, classBytes);
                    return null;
                }
            }, null,
            new FilePermission("<<ALL FILES>>", "read, write"),
            // createDirectories may need it
            new PropertyPermission("user.dir", "read"));
        }
        //加载该内部类
        return UNSAFE.defineAnonymousClass(targetClass, classBytes, null);
    }

上述生成内部类的代码留下了一个问题,lamada方法的具体实现的字节码是怎么跟内部类的目标方法绑定的?lamada方法的具体实现就是一开始调用metafactory方法的入参implMethod。通过搜索implMethod在代码中出现的地方,最终在一个意想不到的地方找到答案,ForwardingMethodGenerator类,该类用于生成具体的lamada方法,原以为该类是ASM定义的标准类,其实是InnerClassLambdaMetafactory的内部类,其关键实现如下:

//TypeConvertingMethodAdapter是ASM定义的类
private class ForwardingMethodGenerator extends TypeConvertingMethodAdapter {
 
        ForwardingMethodGenerator(MethodVisitor mv) {
            super(mv);
        }
 
        void generate(MethodType methodType) {
            visitCode();
 
            //implKind取自implMethod的调用类型,上述测试用例中是invokestatic
            if (implKind == MethodHandleInfo.REF_newInvokeSpecial) {
                visitTypeInsn(NEW, implMethodClassName);
                visitInsn(DUP);
            }
            //处理方法参数
            for (int i = 0; i < argNames.length; i++) {
                visitVarInsn(ALOAD, 0);
                visitFieldInsn(GETFIELD, lambdaClassName, argNames[i], argDescs[i]);
            }
 
            convertArgumentTypes(methodType);
 
            // Invoke the method we want to forward to
            //这部分就是关键了,调用我们期望调用方法,implMethodClassName,implMethodName和implMethodDesc都是从implMethod中取出的属性
            //即目标调用类,方法名,方法描述符,即ASM生成的方法实现实际只是对代码中定义的具体实现的一个调用而已,并未将实现代码对应的字节码
            //转移到生成的类中
            visitMethodInsn(invocationOpcode(), implMethodClassName,
                            implMethodName, implMethodDesc,
                            implDefiningClass.isInterface());
 
            // 对方法调用结果做转换
            Class<?> samReturnClass = methodType.returnType();
            convertType(implMethodReturnClass, samReturnClass, samReturnClass);
            visitInsn(getReturnOpcode(samReturnClass));
            // Maxs computed by ClassWriter.COMPUTE_MAXS,these arguments ignored
            visitMaxs(-1, -1);
            visitEnd();
        }
    }

9、总结
通过执行BootstrapMethods属性中的启动方法,可以获取一个CallSite实例,该实例的target是一个获取一个已实现目标lamada接口(上述测试用例中是java/util/function/Consumer接口)的实例的MethodHandle,invokedynamic调用与之关联的CallSite的MethodHandle实例得到一个Consumer实例,将其放到操作数栈的栈顶,至此invokedynamic指令调用完成。

实现目标lamada接口的类是通过ASM字节码工具在运行时生成的,并非内部类。源代码中实现目标lamada接口的代码会被编译成一个新的隐藏的方法,上述测试用例中因为方法实现中未使用实例变量所以被编译成了一个静态方法。新生成的类实现目标lamada接口方法时实际就只是调用了这个隐藏的方法而已,然后做必要参数和返回值的类型转换。

二、invokedynamic指令
1、使用总结
结合上面lamada实现原理的分析,再来回顾《Java虚拟机规范》中invokedynamic指令的描述,就很好懂了,这里总结如下:

每个invokedynamic指令出现的地方称之为一个动态调用点,每个动态调用点都有对应的调用点限定符,即CONSTANT_InvokeDynamic_info。解析调用点限定符中的bootstrap_method_attr_index属性可以获取调用对应启动方法的MethodHandle实例,解析调用点限定符中的name_and_type_index属性获取与目标CallSite关联的MethodType实例。调用点限定符解析完成,会将下列实例按照顺序依次方法操作数栈中:

入栈完成,JVM会执行启动方法,假如启动方法的对应的符号引用是R,JVM对启动方法有如下要求:

当第一次解析此动态调用点对应的调用限定符时会触发引导方法的调用,如果同时有多个线程尝试解析,则引导方法会被并发调用,因此如果引导方法中需要访问全局数据,则应该做好对全局数据方法的并发访问控制。引导方法执行完成会返回一个java.lang.CallSite或者其子类的实例,这个对象称为调用点对象,此对象的引用将会从操作数栈中出栈,然后发布到所有线程中。如果多个线程同时执行同一个动态调用点的引导方法则JVM必须选择其中的一个返回结果发布到所有线程中,其他线程返回的调用点对象会被直接忽略。调用点对象的MethodType实例必须与从调用限定符中解析出来的MethodType实例一样,调用点对象发布到所有线程后,会与这个动态调用点永久绑定。当执行到此invokedynamic指令时,JVM会实际调用对应CallSite的MethodHandle实例的invokeExact方法,此时操作数栈的内容会被解释成一个指向调用点目标对象的引用,即MethodHandle实例和方法调用的N个参数,这些参数的类型,数量和顺序都与调用限定符中包含的方法描述符保持一致。

2、源码解析:
参考OpenJDK8 hotspot/src/share/vm/interpreter/bytecodeInterpreter.cpp 2444行的实现,源码说明如下:

CASE(_invokedynamic): {
        //如果禁用了InvokeDynamic抛出异常
        if (!EnableInvokeDynamic) {
          // We should not encounter this bytecode if !EnableInvokeDynamic.
          // The verifier will stop it.  However, if we get past the verifier,
          // this will stop the thread in a reasonable way, without crashing the JVM.
          CALL_VM(InterpreterRuntime::throw_IncompatibleClassChangeError(THREAD),
                  handle_exception);
          ShouldNotReachHere();
        }
        
        //往后读取4字节的字节码数据
        u4 index = Bytes::get_native_u4(pc+1);
        //读取常量池中的调用限定符的解析结果
        ConstantPoolCacheEntry* cache = cp->constant_pool()->invokedynamic_cp_cache_entry_at(index);
 
        // We are resolved if the resolved_references field contains a non-null object (CallSite, etc.)
        // This kind of CP cache entry does not need to match the flags byte, because
        // there is a 1-1 relation between bytecode type and CP entry type.
        //如果还未解析,则调用InterpreterRuntime::resolve_invokedynamic方法完成解析
        if (! cache->is_resolved((Bytecodes::Code) opcode)) {
          CALL_VM(InterpreterRuntime::resolve_invokedynamic(THREAD),
                  handle_exception);
          cache = cp->constant_pool()->invokedynamic_cp_cache_entry_at(index);
        }
        //解析完成f1包含一个CallSite对象,作为一个appendix,f1_as_method()方法返回的Method就是对应的MethodHandle绑定的方法
        Method* method = cache->f1_as_method();
        if (VerifyOops) method->verify();
 
        if (cache->has_appendix()) {
          ConstantPool* constants = METHOD->constants();
          //将CallSite对象的引用放入栈顶
          SET_STACK_OBJECT(cache->appendix_if_resolved(constants), 0);
          MORE_STACK(1);
        }
 
        istate->set_msg(call_method);
        //执行方法调用
        istate->set_callee(method);
        istate->set_callee_entry_point(method->from_interpreted_entry());
        //将字节码指针往后移动5个字节
        istate->set_bcp_advance(5);
 
        //profile统计
        BI_PROFILE_UPDATE_CALL();
        //更新PC计数器并返回
        UPDATE_PC_AND_RETURN(0); // I'll be back...
      }
InterpreterRuntime::resolve_invokedynamic方法的源码说明如下:

//第一次调用时会解析调用限定符,创建一个永久绑定的CallSite
IRT_ENTRY(void, InterpreterRuntime::resolve_invokedynamic(JavaThread* thread)) {
  assert(EnableInvokeDynamic, "");
  const Bytecodes::Code bytecode = Bytecodes::_invokedynamic;
 
 
  // resolve method
  CallInfo info;
  //从当前线程正在执行的方法获取该方法所属的类的常量池
  constantPoolHandle pool(thread, method(thread)->constants());
  //从字节码中读取调用限定符在常量池中的索引
  int index = get_index_u4(thread, bytecode);
  {
    JvmtiHideSingleStepping jhss(thread);
    //解析调用限定符,解析的结果保存在CallInfo中,其中_resolved_method属性就是CallSite最终关联的方法
    LinkResolver::resolve_invoke(info, Handle(), pool,
                                 index, bytecode, CHECK);
  } // end JvmtiHideSingleStepping
 
  //将解析结果保存到常量池中
  ConstantPoolCacheEntry* cp_cache_entry = pool->invokedynamic_cp_cache_entry_at(index);
  cp_cache_entry->set_dynamic_call(pool, info);
}
IRT_END
上一篇 下一篇

猜你喜欢

热点阅读