几年前曾经写过一篇Lambda表达式实现机制的分析,现在回头看存在一些错误和疏漏,有误导嫌疑,因此重写一版订正。
平台需求:本文所示代码需要在jdk 15上编译和运行。
下面代码为一个Lambda表达式的例子
package demo; import java.util.LinkedList; import java.util.List; import java.util.stream.Collectors; public class Test1 { public void func1(List<Integer> list) { if (list == null) { return; } var newList = list.stream() .map(x -> { System.out.println("in original lambda " + x); return x + x; }) .collect(Collectors.toList()); var s = """ oldList = %s newList = %s """; System.out.println(String.format(s, list, newList)); } public static void main(String[] args) { var t1 = new Test1(); var list = new LinkedList<Integer>(); list.add(1); list.add(2); t1.func1(list); } }使用javap -c -p -v Test1.class反编译,主要字节码如下所示:
public void func1(java.util.List<java.lang.Integer>); descriptor: (Ljava/util/List;)V flags: (0x0001) ACC_PUBLIC Code: stack=6, locals=4, args_size=2 0: aload_1 1: ifnonnull 5 4: return 5: aload_1 6: invokeinterface #7, 1 // InterfaceMethod java/util/List.stream:()Ljava/util/stream/Stream; 11: invokedynamic #13, 0 // InvokeDynamic #0:apply:()Ljava/util/function/Function; 16: invokeinterface #17, 2 // InterfaceMethod java/util/stream/Stream.map:(Ljava/util/function/Function;)Ljava/util/stream/Stream; 21: invokestatic #23 // Method java/util/stream/Collectors.toList:()Ljava/util/stream/Collector; 24: invokeinterface #29, 2 // InterfaceMethod java/util/stream/Stream.collect:(Ljava/util/stream/Collector;)Ljava/lang/Object; 29: checkcast #8 // class java/util/List 32: astore_2 33: ldc #33 // String oldList = %s\n newList = %s\n 35: astore_3 36: getstatic #35 // Field java/lang/System.out:Ljava/io/PrintStream; 39: aload_3 40: iconst_2 41: anewarray #2 // class java/lang/Object 44: dup 45: iconst_0 46: aload_1 47: aastore 48: dup 49: iconst_1 50: aload_2 51: aastore 52: invokestatic #41 // Method java/lang/String.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String; 55: invokevirtual #47 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 58: return LineNumberTable: line 9: 0 line 10: 4 line 13: 5 line 14: 16 line 18: 21 line 19: 33 line 23: 36 line 24: 58 LocalVariableTable: Start Length Slot Name Signature 0 59 0 this Ldemo/Test1; 0 59 1 list Ljava/util/List; 33 26 2 newList Ljava/util/List; 36 23 3 s Ljava/lang/String; LocalVariableTypeTable: Start Length Slot Name Signature 0 59 1 list Ljava/util/List<Ljava/lang/Integer;>; 33 26 2 newList Ljava/util/List<Ljava/lang/Integer;>; StackMapTable: number_of_entries = 1 frame_type = 5 /* same */ Signature: #95 // (Ljava/util/List<Ljava/lang/Integer;>;)V private static java.lang.Integer lambda$func1$0(java.lang.Integer); descriptor: (Ljava/lang/Integer;)Ljava/lang/Integer; flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC Code: stack=2, locals=1, args_size=1 0: getstatic #35 // Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_0 4: invokedynamic #73, 0 // InvokeDynamic #1:makeConcatWithConstants:(Ljava/lang/Integer;)Ljava/lang/String; 9: invokevirtual #47 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 12: aload_0 13: invokevirtual #77 // Method java/lang/Integer.intValue:()I 16: aload_0 17: invokevirtual #77 // Method java/lang/Integer.intValue:()I 20: iadd 21: invokestatic #59 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 24: areturn LineNumberTable: line 15: 0 line 16: 12 LocalVariableTable: Start Length Slot Name Signature 0 25 0 x Ljava/lang/Integer; BootstrapMethods: 0: #110 REF_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: #117 (Ljava/lang/Object;)Ljava/lang/Object; #119 REF_invokeStatic demo/Test1.lambda$func1$0:(Ljava/lang/Integer;)Ljava/lang/Integer; #122 (Ljava/lang/Integer;)Ljava/lang/Integer; 1: #123 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; Method arguments: #129 in original lambda \u0001首先可以看见,javac自动添加了一个新的私有静态方法lambda$func1$0,方法体内即是Lambda表达式中的逻辑。
索引11处即为对Lambda表达式的调用。
11: invokedynamic #13, 0 // InvokeDynamic #0:apply:()Ljava/util/function/Function;根据JVM虚拟机规范,invokedynamic指令格式为:invokedynamic indexbyte1 indexbyte2 0 0。indexbyte1 indexbyte2共同组成了指向当前类常量池的索引值,该索引值对应的常量池项称为动态调用点。在我们的例子中,indexbyte1 indexbyte2组成的索引值为13,相关的常量池项如下:
#13 = InvokeDynamic #0:#14 // #0:apply:()Ljava/util/function/Function; #14 = NameAndType #15:#16 // apply:()Ljava/util/function/Function; #15 = Utf8 apply #16 = Utf8 ()Ljava/util/function/Function;而根据JVM虚拟机规范,常量池中CONSTANT_InvokeDynamic_info项的结构如下:
CONSTANT_InvokeDynamic_info { u1 tag; u2 bootstrap_method_attr_index; u2 name_and_type_index; } tag固定为18,表示CONSTANT_InvokeDynamic_info类型;bootstrap_method_attr_index,是指向引导方法表bootstrap_methods[]数组的索引,在例子中为0;name_and_type_index,是指向常量池CONSTANT_NameAndType_info项的索引,在例子中为14。通常的CONSTANT_NameAndType_info结构用来表示字段或者方法,结构如下:
CONSTANT_NameAndType_info { u1 tag; u2 name_index; u2 descriptionor_index; } tag固定为12,表示CONSTANT_NameAndType_info类型;name_index和descriptionor_index都是指向常量池CONSTANT_Utf8_info的索引,在例程中为15和16,分别是方法名和类名。我们再看看bootstrap_methods[]数组,bootstrap_methods[]数组位于class文件最后,如下:
BootstrapMethods: 0: #110 REF_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: #117 (Ljava/lang/Object;)Ljava/lang/Object; #119 REF_invokeStatic demo/Test1.lambda$func1$0:(Ljava/lang/Integer;)Ljava/lang/Integer; #122 (Ljava/lang/Integer;)Ljava/lang/Integer; 1: #123 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; Method arguments: #129 in original lambda \u0001BootstrapMethods有两个元素,索引0对应的是Lambda调用,索引1对应的是Test1 class 15行的字符串拼接。 字符串拼接的逻辑从JDK 9开始,使用动态分派替代原来的StringBuilder append方式,以提升执行效率,并为后续改进提供更大的灵活性。
与BootstrapMethods[0]相关的常量池项如下:
#110 = MethodHandle 6:#111 // REF_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; #111 = Methodref #112.#113 // 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; #112 = Class #114 // java/lang/invoke/LambdaMetafactory #113 = NameAndType #115:#116 // 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; #114 = Utf8 java/lang/invoke/LambdaMetafactory #115 = Utf8 metafactory #116 = 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; #117 = MethodType #118 // (Ljava/lang/Object;)Ljava/lang/Object; #118 = Utf8 (Ljava/lang/Object;)Ljava/lang/Object; #119 = MethodHandle 6:#120 // REF_invokeStatic demo/Test1.lambda$func1$0:(Ljava/lang/Integer;)Ljava/lang/Integer; #120 = Methodref #53.#121 // demo/Test1.lambda$func1$0:(Ljava/lang/Integer;)Ljava/lang/Integer; #121 = NameAndType #103:#104 // lambda$func1$0:(Ljava/lang/Integer;)Ljava/lang/Integer; #122 = MethodType #104 // (Ljava/lang/Integer;)Ljava/lang/Integer;常量池项110是一个方法句柄对象,该方法句柄指向java.lang.invoke.LambdaMetafactory类的静态方法metafactory方法,metafactory方法返回一个调用点CallSite对象。
LambdaMetafactory.metafactory方法共6个入参,前3个对于所有的Bootstrap Method启动方法都是固定的,分别为lookup上下文,调用方法名,方法签名类型(入参为Lambda表达式捕获参数),由于是invokedynamic调用,由JVM自动压入操作数栈;后3个入参如下: * 常量池项117,上文提到的javac自动创建的私有静态方法的方法签名类型MethodType,并已擦除泛型信息;该信息将用于构建隐藏内部类; * 常量池项119,上文提到的javac自动创建的私有静态方法的方法句柄MethodHandle; * 常量池项122,如果目标方法是泛型方法,则替换泛型为具体类型,如果不是泛型方法,则与常量池项117相同;该信息用于在隐藏内部类内,借助invokestatic指令,调用javac自动创建的私有静态方法。静态方法metafactory是启动方法,metafactory方法有6个入参,详细描述见上一节;metafactory返回一个调用点CallSite对象。调用方获取CallSite对象后,可以该CallSite对象绑定的方法句柄(target变量),进而通过方法句柄的invoke*方法,调用目标方法。
public static CallSite metafactory(MethodHandles.Lookup caller, String invokedName, MethodType invokedType, MethodType samMethodType, MethodHandle implMethod, MethodType instantiatedMethodType) throws LambdaConversionException { AbstractValidatingLambdaMetafactory mf; // 创建InnerClassLambdaMetafactory实例 mf = new InnerClassLambdaMetafactory(caller, invokedType, invokedName, samMethodType, implMethod, instantiatedMethodType, false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY); // 验证方法入参是否合法 mf.validateMetafactoryArgs(); // 构建调用点CallSite return mf.buildCallSite(); } private static String lambdaClassName(Class<?> targetClass) { // Lambda内部类类名为,原类名$$Lambda$数字,如本文例程为:Test1$$Lambda$1 String name = targetClass.getName(); if (targetClass.isHidden()) { // use the original class name name = name.replace('/', '_'); } return name.replace('.', '/') + "$$Lambda$" + counter.incrementAndGet(); }通过设置参数-Djdk.internal.lambda.dumpProxyClasses ,就可以dump Lambda内部类到文件系统中,然后通过javap命令将该内部类反编译,字节码如下:
final class demo.Test1$$Lambda$1 implements java.util.function.Function minor version: 0 major version: 52 flags: (0x1030) ACC_FINAL, ACC_SUPER, ACC_SYNTHETIC this_class: #2 // demo/Test1$$Lambda$1 super_class: #4 // java/lang/Object interfaces: 1, fields: 0, methods: 2, attributes: 0 Constant pool: #1 = Utf8 demo/Test1$$Lambda$1 #2 = Class #1 // demo/Test1$$Lambda$1 #3 = Utf8 java/lang/Object #4 = Class #3 // java/lang/Object #5 = Utf8 java/util/function/Function #6 = Class #5 // java/util/function/Function #7 = Utf8 <init> #8 = Utf8 ()V #9 = NameAndType #7:#8 // "<init>":()V #10 = Methodref #4.#9 // java/lang/Object."<init>":()V #11 = Utf8 apply #12 = Utf8 (Ljava/lang/Object;)Ljava/lang/Object; #13 = Utf8 java/lang/Integer #14 = Class #13 // java/lang/Integer #15 = Utf8 demo/Test1 #16 = Class #15 // demo/Test1 #17 = Utf8 lambda$func1$0 #18 = Utf8 (Ljava/lang/Integer;)Ljava/lang/Integer; #19 = NameAndType #17:#18 // lambda$func1$0:(Ljava/lang/Integer;)Ljava/lang/Integer; #20 = Methodref #16.#19 // demo/Test1.lambda$func1$0:(Ljava/lang/Integer;)Ljava/lang/Integer; #21 = Utf8 Code { private demo.Test1$$Lambda$1(); descriptor: ()V flags: (0x0002) ACC_PRIVATE Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #10 // Method java/lang/Object."<init>":()V 4: return public java.lang.Object apply(java.lang.Object); descriptor: (Ljava/lang/Object;)Ljava/lang/Object; flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=2, args_size=2 0: aload_1 1: checkcast #14 // class java/lang/Integer 4: invokestatic #20 // Method demo/Test1.lambda$func1$0:(Ljava/lang/Integer;)Ljava/lang/Integer; 7: areturn } 内部类实现了java.util.function.Function接口,由于泛型在编译期被擦除,apply方法入参出参均为java.lang.Object;由于例程中的Lambda没有捕获变量,内部类构造函数没有入参;解释器代码如下:
CASE(_invokedynamic): { u4 index = Bytes::get_native_u4(pc+1); ConstantPoolCacheEntry* cache = cp->constant_pool()->invokedynamic_cp_cache_entry_at(index); // 解析符号引用,如果解析过了则从cache中获取直接引用 // 解析关键逻辑在InterpreterRuntime resolve_from_cache函数 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); } // 通常f1是非虚方法调用的指针,但是对于invokedynamic和invokehandle指令,f1保存了实际方法的指针 Method* method = cache->f1_as_method(); if (VerifyOops) method->verify(); if (cache->has_appendix()) { constantPoolHandle cp(THREAD, METHOD->constants()); SET_STACK_OBJECT(cache->appendix_if_resolved(cp), 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... }符号引用解析如下:
void InterpreterRuntime::resolve_invokedynamic(JavaThread* thread) { Thread* THREAD = thread; LastFrameAccessor last_frame(thread); const Bytecodes::Code bytecode = Bytecodes::_invokedynamic; // 解析方法调用 CallInfo info; constantPoolHandle pool(thread, last_frame.method()->constants()); int index = last_frame.get_index_u4(bytecode); { JvmtiHideSingleStepping jhss(thread); // LinkResolver::resolve_invoke(info, Handle(), pool, index, bytecode, CHECK); } // 设置常量池Cache ConstantPoolCacheEntry* cp_cache_entry = pool->invokedynamic_cp_cache_entry_at(index); cp_cache_entry->set_dynamic_call(pool, info); }InterpreterRuntime resolve_invokedynamic又调用了LinkResolver的resolve_invoke函数进行解析。
void LinkResolver::resolve_invoke(CallInfo& result, Handle recv, const constantPoolHandle& pool, int index, Bytecodes::Code byte, TRAPS) { switch (byte) { case Bytecodes::_invokestatic : resolve_invokestatic (result, pool, index, CHECK); break; case Bytecodes::_invokespecial : resolve_invokespecial (result, recv, pool, index, CHECK); break; case Bytecodes::_invokevirtual : resolve_invokevirtual (result, recv, pool, index, CHECK); break; case Bytecodes::_invokehandle : resolve_invokehandle (result, pool, index, CHECK); break; case Bytecodes::_invokedynamic : resolve_invokedynamic (result, pool, index, CHECK); break; case Bytecodes::_invokeinterface: resolve_invokeinterface(result, recv, pool, index, CHECK); break; default : break; } return; } void LinkResolver::resolve_invokedynamic(CallInfo& result, const constantPoolHandle& pool, int indy_index, TRAPS) { ConstantPoolCacheEntry* cpce = pool->invokedynamic_cp_cache_entry_at(indy_index); int pool_index = cpce->constant_pool_index(); // 解析bootstrap方法 BootstrapInfo bootstrap_specifier(pool, pool_index, indy_index); // 检查调用点CallSite是否已经存在了 { bool is_done = bootstrap_specifier.resolve_previously_linked_invokedynamic(result, CHECK); if (is_done) return; } // 如果不存在则调用resolve_dynamic_call解析 resolve_dynamic_call(result, bootstrap_specifier, CHECK); if (TraceMethodHandles) { bootstrap_specifier.print_msg_on(tty, "resolve_invokedynamic"); } // 实际方法的直接引用存在f1指针 } void LinkResolver::resolve_dynamic_call(CallInfo& result, BootstrapInfo& bootstrap_specifier, TRAPS) { // 返回JAVA,调用启动方法,获取实际方法的方法句柄 SystemDictionary::invoke_bootstrap_method(bootstrap_specifier, THREAD); Exceptions::wrap_dynamic_exception(THREAD); if (HAS_PENDING_EXCEPTION) { if (!PENDING_EXCEPTION->is_a(SystemDictionary::LinkageError_klass())) { return; } bool recorded_res_status = bootstrap_specifier.save_and_throw_indy_exc(CHECK); if (!recorded_res_status) { bool is_done = bootstrap_specifier.resolve_previously_linked_invokedynamic(result, CHECK); if (is_done) return; } assert(bootstrap_specifier.invokedynamic_cp_cache_entry()->indy_resolution_failed(), "Resolution failure flag wasn't set"); } bootstrap_specifier.resolve_newly_linked_invokedynamic(result, CHECK); }调用启动方法的逻辑如下:
void SystemDictionary::invoke_bootstrap_method(BootstrapInfo& bootstrap_specifier, TRAPS) { bootstrap_specifier.resolve_bsm(CHECK); if (bootstrap_specifier.caller() == NULL || bootstrap_specifier.type_arg().is_null()) { THROW_MSG(vmSymbols::java_lang_InternalError(), "Invalid bootstrap method invocation with no caller or type argument"); } bool is_indy = bootstrap_specifier.is_method_call(); objArrayHandle appendix_box; if (is_indy) { // 处理启动方法的附加参数,如上文提到的metafactory的后三个参数 appendix_box = oopFactory::new_objArray_handle(SystemDictionary::Object_klass(), 1, CHECK); assert(appendix_box->obj_at(0) == NULL, ""); } JavaCallArguments args; args.push_oop(Handle(THREAD, bootstrap_specifier.caller_mirror())); args.push_int(bootstrap_specifier.bss_index()); args.push_oop(bootstrap_specifier.bsm()); args.push_oop(bootstrap_specifier.name_arg()); args.push_oop(bootstrap_specifier.type_arg()); args.push_oop(bootstrap_specifier.arg_values()); if (is_indy) { args.push_oop(appendix_box); } JavaValue result(T_OBJECT); // 通过JavaCalls调用启动方法 JavaCalls::call_static(&result, SystemDictionary::MethodHandleNatives_klass(), is_indy ? vmSymbols::linkCallSite_name() : vmSymbols::linkDynamicConstant_name(), is_indy ? vmSymbols::linkCallSite_signature() : vmSymbols::linkDynamicConstant_signature(), &args, CHECK); Handle value(THREAD, (oop) result.get_jobject()); if (is_indy) { // 获取实际方法的方法句柄 Handle appendix; Method* method = unpack_method_and_appendix(value, bootstrap_specifier.caller(), appendix_box, &appendix, CHECK); methodHandle mh(THREAD, method); bootstrap_specifier.set_resolved_method(mh, appendix); } else { bootstrap_specifier.set_resolved_value(value); } assert(bootstrap_specifier.is_resolved() || (bootstrap_specifier.is_method_call() && bootstrap_specifier.resolved_method().not_null()), "bootstrap method call failed"); }在设计Lambda表达式时,Oracle的开发人员考虑过多种方案,如内部匿名类、方法句柄、invokedynamic等,最终选择invokedynamic,主要出于两方面的考量:
为未来的优化提供最大的灵活性保持类的字节码格式稳定采用invokedynamic指令,将方法分派的具体逻辑放在LambdaMetafactory中,并将内部类的创建时机推迟到运行时。如果未来需要修改Lambda表达式的分配和调用方式,开发者仅需更新LambdaMetafactory逻辑即可,而不需要修改class文件格式。
jdk 15 Translation of Lambda Expressions