方法引用(Method reference)和invokedynamic指令详细分析

invokedynamic是jvm指令集里面最复杂的一条。本文将详细分析invokedynamic指令是如何实现方法引用(Method reference)的。

具体言之,有这样一个方法引用:

interface Encode {
    void encode(Derive person);
}
class Base {
    public void encrypt() {
        System.out.println("Base::speak");
    }
}
class Derive extends Base {
    @Override
    public void encrypt() {
        System.out.println("Derive::speak");
    }
}
public class MethodReference {
    public static void main(String[] args) {
        Encode encode = Base::encrypt;
        System.out.println(encode);
    }
}

使用javap -verbose MethodReference.class查看对应字节码:

// 常量池
Constant pool:
   #1 = Methodref          #6.#22         // java/lang/Object."<init>":()V
   #2 = InvokeDynamic      #0:#27         // #0:encode:()LEncode;
   #3 = Fieldref           #28.#29        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = Methodref          #30.#31        // java/io/PrintStream.println:(Ljava/lang/Object;)V
   #5 = Class              #32            // MethodReference
   #6 = Class              #33            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               LMethodReference;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               encode
  #19 = Utf8               LEncode;
  #20 = Utf8               SourceFile
  #21 = Utf8               MethodReference.java
  #22 = NameAndType        #7:#8          // "<init>":()V
  #23 = Utf8               BootstrapMethods
  #24 = MethodHandle       #6:#34         // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;L
java/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang
/invoke/CallSite;
  #25 = MethodType         #35            //  (LDerive;)V
  #26 = MethodHandle       #5:#36         // invokevirtual Base.encrypt:()V
  #27 = NameAndType        #18:#37        // encode:()LEncode;
  #28 = Class              #38            // java/lang/System
  #29 = NameAndType        #39:#40        // out:Ljava/io/PrintStream;
  #30 = Class              #41            // java/io/PrintStream
  #31 = NameAndType        #42:#43        // println:(Ljava/lang/Object;)V
  #32 = Utf8               MethodReference
  #33 = Utf8               java/lang/Object
  #34 = Methodref          #44.#45        // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/Str
ing;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallS
ite;
  #35 = Utf8               (LDerive;)V
  #36 = Methodref          #46.#47        // Base.encrypt:()V
  #37 = Utf8               ()LEncode;
  #38 = Utf8               java/lang/System
  #39 = Utf8               out
  #40 = Utf8               Ljava/io/PrintStream;
  #41 = Utf8               java/io/PrintStream
  #42 = Utf8               println
  #43 = Utf8               (Ljava/lang/Object;)V
  #44 = Class              #48            // java/lang/invoke/LambdaMetafactory
  #45 = NameAndType        #49:#53        // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Lj
ava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #46 = Class              #54            // Base
  #47 = NameAndType        #55:#8         // encrypt:()V
  #48 = Utf8               java/lang/invoke/LambdaMetafactory
  #49 = Utf8               metafactory

// 字节码指令
 public static void main(java.lang.String[]);
     0: invokedynamic #2,  0              // InvokeDynamic #0:encode:()LEncode;
     5: astore_1
     6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
     9: aload_1
    10: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
    13: return

// 属性
SourceFile: "MethodReference.java"
InnerClasses:
     public static final #51= #50 of #56; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #24 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/Method
Type;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #25 (LDerive;)V
      #26 invokevirtual Base.encrypt:()V
      #25 (LDerive;)V

使用invokedynamic指令生成encode对象,然后存入局部变量槽#1。接着获取getstatic获取java/lang/System类的out字段,最后局部变量槽#1作为参数压栈,invokevirtual虚函数调用System.outprintln方法。

那么invokedynamic到底是怎么生成encode对象的呢?

1.虚拟机解析

hotspotinvokedynamic指令的解释如下:

      CASE(_invokedynamic): {

        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.
        if (! cache->is_resolved((Bytecodes::Code) opcode)) {
          CALL_VM(InterpreterRuntime::resolve_from_cache(THREAD, (Bytecodes::Code)opcode),
                  handle_exception);
          cache = cp->constant_pool()->invokedynamic_cp_cache_entry_at(index);
        }

        Method* method = cache->f1_as_method();
        if (VerifyOops) method->verify();

        if (cache->has_appendix()) {
          ConstantPool* constants = METHOD->constants();
          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());
        istate->set_bcp_advance(5);

        // Invokedynamic has got a call counter, just like an invokestatic -> increment!
        BI_PROFILE_UPDATE_CALL();

        UPDATE_PC_AND_RETURN(0); // I'll be back...
      }

使用invokedynamic_cp_cache_entry_at获取常量池对象,然后检查是否已经解析过,如果没有就解析反之复用,然后设置方法字节码,留待后面解释执行。那么,重点是这个解析。我们对照着jvm spec来看。

根据jvm文档的描述,invokedynamic的操作数(operand)指向常量池一个动态调用点描述符(dynamic call site specifier)。
动态调用点描述符是一个CONSTANT_InvokeDynamic_info结构体:

CONSTANT_InvokeDynamic_info {
 u1 tag;
 u2 bootstrap_method_attr_index;
 u2 name_and_type_index;
}
  • tag 表示这个结构体的常量,不用管
  • bootstrap_method_attr_index 启动方法数组
  • name_and_type_index 一个名字+类型的描述字段,就像这样Object p放到虚拟机里面表示是Ljava/lang/Object; p

然后启动方法数组结构是这样:

BootstrapMethods_attribute {
 ...
 u2 num_bootstrap_methods;
 {
    u2 bootstrap_method_ref;
    u2 num_bootstrap_arguments;
    u2 bootstrap_arguments[num_boot]
    } bootstrap_methods[num_bootstrap_methods];
}

就是一个数组,每个元素是{指向MethodHandle的索引,启动方法参数个数,启动方法参数}

MethodlHandle是个非常重要的结构,指导了虚拟机对于这个启动方法的解析,先关注一下这个结构:

CONSTANT_MethodHandle_info {
 u1 tag;//表示该结构体的常量tag,可以忽略
 u1 reference_kind;
 u2 reference_index;
}
  • reference_kind是[1,9]的数,它表示这个method handle的类型,这个字段和字节码的行为有关。
  • reference_index 根据reference_kind会指向常量池的不同类型,具体来说
    • reference_kind==1,3,4 指向CONSTANT_Fieldref_info结构,表示一个类的字段
    • reference_kind==5,8,指向CONSTANT_Methodref_info,表示一个类的方法
    • reference_kind==6,7, 同上,只是兼具接口的方法或者类的方法的可能。
    • reference_kind==9,指向CONSTATN_InterfaceMethodref_info,表示一个接口方法

通过invokedynamic,我们可以得

  1. 名字+描述符的表示(由name_and_type_index给出)
  2. 一个启动方法数组(由bootstrap_method_attr_index给出)

2.手动解析

可以手动模拟一下解析,看看最后得到的数据是什么样的。在这个例子中:

  0: invokedynamic #2,  0   //第二个operand总是0

查看常量池#2项:

#2 = InvokeDynamic      #0:#27         // #0:encode:()LEncode;
#27 = NameAndType        #18:#37        // encode:()LEncode;

BootstrapMethods:
  0: #24 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/Method
Type;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #25 (LDerive;)V
      #26 invokevirtual Base.encrypt:()V
      #25 (LDerive;)V

得到的名字+描述符是:Encode.encode(),启动方法数组有一个元素,回忆下之前说的,这个元素构成如下:

{指向MethodHandle的索引,启动方法参数个数,启动方法参数}

这里得到的MethodHandle表示的是LambdaMetafactory.metafactory:

#24 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/Method
Type;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;`

启动方法参数有:

  • #25 (LDerive;)V
  • #26 invokevirtual Base.encrypt:()V
  • #25 (LDerive;)V

3. java.lang.invoke.LambdaMetafactory

先说说LambdaMetafactory有什么用。javadoc给出的解释是:

LambdaMetafactory方便我们创建简单的"函数对象",这些函数对象通过代理MethodHandle实现了一些接口。
当这个函数返回的CallSite被调用的时候,会产生一个类的实例,该类还实现了一些方法,具体由参数给出

将上面得到的MethodHandle写得更可读就是调用的这个方法:

   public static CallSite LambdaMetafactory.metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType);

六个参数,慢慢来。

3.1 LambdaMetafactory.metafactory()调用前

要知道参数是什么意思,可以从它的调用者来管中窥豹:

 static CallSite makeSite(MethodHandle bootstrapMethod,
                             // Callee information:
                             String name, MethodType type,
                             // Extra arguments for BSM, if any:
                             Object info,
                             // Caller information:
                             Class<?> callerClass) {
        MethodHandles.Lookup caller = IMPL_LOOKUP.in(callerClass);
        CallSite site;
        try {
            Object binding;
            info = maybeReBox(info);
            if (info == null) {
                binding = bootstrapMethod.invoke(caller, name, type);
            } else if (!info.getClass().isArray()) {
                binding = bootstrapMethod.invoke(caller, name, type, info);
            } else {
                Object[] argv = (Object[]) info;
                maybeReBoxElements(argv);
                switch (argv.length) {
                ...
                case 3:
                    binding = bootstrapMethod.invoke(caller, name, type,
                                                     argv[0], argv[1], argv[2]);
                    break;
                ...
                }
            }
            //System.out.println("BSM for "+name+type+" => "+binding);
            if (binding instanceof CallSite) {
                site = (CallSite) binding;
            }  else {
                throw new ClassCastException("bootstrap method failed to produce a CallSite");
            }
            ...
        } catch (Throwable ex) {
            ...
        }
        return site;
    }

java.lang.invoke.LambdaMetafactory的调用是通过MethodHandle引发的,所以可能还需要补一下MethodHandle的用法,百度一搜一大堆,javadoc也给出了使用示例:

String s;
MethodType mt; MethodHandle mh;
MethodHandles.Lookup lookup = MethodHandles.lookup();
// mt is (char,char)String
mt = MethodType.methodType(String.class, char.class, char.class);
mh = lookup.findVirtual(String.class, "replace", mt);
s = (String) mh.invoke("daddy",'d','n');
// invokeExact(Ljava/lang/String;CC)Ljava/lang/String;
assertEquals(s, "nanny");

回到源码,关键是这句:

binding = bootstrapMethod.invoke(caller, name, type,
                               argv[0], argv[1], argv[2]);

argv[0],argv[1],argv[2]分别表示之前启动方法的三个参数
caller即调用者,这里是MethodReference这个类,然后name和type参见下面的详细解释:

  • MethodHandles.Lookup caller 表示哪个类引发了调动
  • String invokedName 表示生成的类的方法名,对应例子的encode
  • MethodType invokedType 表示CallSite的函数签名,其中参数类型表示捕获变量的类型,返回类型是类要实现的接口的名字,对应例子的()Encode,即要生成一个类,这个类没有捕获自由变量(所以参数类为空),然后这个类要实现Encode接口(返回类型为生成的类要实现的接口)
    接下来
  • MethodType samMethodType 表示要实现的方法的函数签名和返回值,对于例子的#25 (LDerive;)V,即实现方法带有一个形参,返回void
  • MethodHandle implMethod 表示实现的方法里面应该调用的函数,对于例子的#26 invokevirtual Base.encrypt:()V,表示调用Base的虚函数encrypt,返回void
  • MethodType instantiatedMethodType 表示调用方法的运行时描述符,如果不是泛型就和samMethodType一样

3.2 LambdaMetafactory.metafactory()调用

源码面前,不是了无秘密吗hhh,点进源码看看这个LambdaMetafactory到底做了什么:

     */
    public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType)
            throws LambdaConversionException {
        AbstractValidatingLambdaMetafactory mf;
        mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                             invokedName, samMethodType,
                                             implMethod, instantiatedMethodType,
                                             false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
        mf.validateMetafactoryArgs();
        return mf.buildCallSite();
    }

它什么也没做,做事的是InnerClassLambdaMetafactory.buildCallSite()创建的最后CallSite,那就进一步看看InnerClassLambdaMetafactory.buildCallSite()

    @Override
    CallSite buildCallSite() throws LambdaConversionException {
        // 1. 创建生成的类对象
        final Class<?> innerClass = spinInnerClass();
        if (invokedType.parameterCount() == 0) {
            // 2. 用反射获取构造函数
            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 {
                // 3. 创建实例
                Object inst = ctrs[0].newInstance();
                // 4. 根据实例和samBase(接口类型)生成MethodHandle
                // 5. 生成ConstantCallSite
                return new ConstantCallSite(MethodHandles.constant(samBase, inst));
            }
            catch (ReflectiveOperationException e) {
                throw new LambdaConversionException("Exception instantiating lambda object", e);
            }
        } else {
            try {
                UNSAFE.ensureClassInitialized(innerClass);
                return new ConstantCallSite(
                        MethodHandles.Lookup.IMPL_LOOKUP
                             .findStatic(innerClass, NAME_FACTORY, invokedType));
            }
            catch (ReflectiveOperationException e) {
                throw new LambdaConversionException("Exception finding constructor", e);
            }
        }
    }

首先它生成一个.class文件,虚拟机默认不会输出,需要下面设置VM option-Djdk.internal.lambda.dumpProxyClasses=.,Dump出虚拟机生成的类我得到的是:

import java.lang.invoke.LambdaForm.Hidden;

// $FF: synthetic class
final class MethodReference$$Lambda$1 implements Encode {
    private MethodReference$$Lambda$1() {
    }

    @Hidden
    public void encode(Derive var1) {
        ((Base)var1).encrypt();
    }
}

该类实现了传来的接口函数(动态类生成,熟悉spring的朋友应该很熟悉)。

回到buildCallSite()源码,它使用MethodHandles.constant(samBase, inst)创建MethdHandle,放到CallSite里面,完成整个LambdaMetafactory的工作。
MethodHandles.constant(samBase, inst)相当于一个总是返回inst的方法。

总结

到这里就结束了整个流程,文章有点长,总结一下:

  1. 虚拟机遇到invokedynamic,开始解析操作数
  2. 根据invokedynamic #0:#27获取到启动方法(#0)和一个名字+描述符(#27)
    其中启动方法是
BootstrapMethods:
  0: #24 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/Method
Type;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #25 (LDerive;)V
      #26 invokevirtual Base.encrypt:()V
      #25 (LDerive;)V

名字+描述符

 #27 = NameAndType        #18:#37        // encode:()LEncode;
  1. 启动方法指向LambdaMetafactory.metafactory,但是不会直接调用而是通过MethdHandle间接调用。调用位置位于CallSite.makeCallSite()
  2. LambdaMetafactory.metafactory()其实使用InnerClassLambdaMetafactory.buildCallSite()创建了最后的CallSite
  3. buildCallSite()会创建一个.class,
  4. buildCallSite()会向最后的CallSite里面放入一个可调用的MethdHandle
  5. 这个MethodHandle指向的是一个总是返回刚刚创建的.class类的实例的方法,由MethodHandles.constant(samBase, inst)完成
  6. 最后,用invokevirtual调用CallSite里面的MethdHandle,返回.class类的示例,即inst,即new MethodReference$$Lambda$1
01-05 04:52