Java中invokedynamic字节码指令问题
1.方法引用和invokedynamic
invokedynamic是jvm指令集里面最复杂的一条。本文将从高观点的角度下分析invokedynamic指令是如何实现方法引用(Methodreference)的。
具体言之,有这样一个方法引用:
interfaceEncode{ voidencode(Deriveperson); } classBase{ publicvoidencrypt(){ System.out.println("Base::speak"); } } classDeriveextendsBase{ @Override publicvoidencrypt(){ System.out.println("Derive::speak"); } } publicclassMethodReference{ publicstaticvoidmain(String[]args){ Encodeencode=Base::encrypt; System.out.println(encode); } }
使用javap-verboseMethodReference.class查看对应字节码:
//常量池 Constantpool: #1=Methodref#6.#22//java/lang/Object."":()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 #8=Utf8()V #9=Utf8Code #10=Utf8LineNumberTable #11=Utf8LocalVariableTable #12=Utf8this #13=Utf8LMethodReference; #14=Utf8main #15=Utf8([Ljava/lang/String;)V #16=Utf8args #17=Utf8[Ljava/lang/String; #18=Utf8encode #19=Utf8LEncode; #20=Utf8SourceFile #21=Utf8MethodReference.java #22=NameAndType#7:#8//" ":()V #23=Utf8BootstrapMethods #24=MethodHandle#6:#34//invokestaticjava/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//invokevirtualBase.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=Utf8MethodReference #33=Utf8java/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=Utf8java/lang/System #39=Utf8out #40=Utf8Ljava/io/PrintStream; #41=Utf8java/io/PrintStream #42=Utf8println #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=Utf8java/lang/invoke/LambdaMetafactory #49=Utf8metafactory //字节码指令 publicstaticvoidmain(java.lang.String[]); 0:invokedynamic#2,0//InvokeDynamic#0:encode:()LEncode; 5:astore_1 6:getstatic#3//Fieldjava/lang/System.out:Ljava/io/PrintStream; 9:aload_1 10:invokevirtual#4//Methodjava/io/PrintStream.println:(Ljava/lang/Object;)V 13:return //属性 SourceFile:"MethodReference.java" InnerClasses: publicstaticfinal#51=#50of#56;//Lookup=classjava/lang/invoke/MethodHandles$Lookupofclassjava/lang/invoke/MethodHandles BootstrapMethods: 0:#24invokestaticjava/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; Methodarguments: #25(LDerive;)V #26invokevirtualBase.encrypt:()V #25(LDerive;)V
使用invokedynamic指令生成encode对象,然后存入局部变量槽#1。接着获取getstatic获取java/lang/System类的out字段,最后局部变量槽#1作为参数压栈,invokevirtual虚函数调用System.out的println方法。
那么invokedynamic到底是怎么生成encode对象的呢?
1.虚拟机解析
hotspot对invokedynamic指令的解释如下:
CASE(_invokedynamic):{ u4index=Bytes::get_native_u4(pc+1); ConstantPoolCacheEntry*cache=cp->constant_pool()->invokedynamic_cp_cache_entry_at(index); //Weareresolvediftheresolved_referencesfieldcontainsanon-nullobject(CallSite,etc.) //ThiskindofCPcacheentrydoesnotneedtomatchtheflagsbyte,because //thereisa1-1relationbetweenbytecodetypeandCPentrytype. 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); //Invokedynamichasgotacallcounter,justlikeaninvokestatic->increment! BI_PROFILE_UPDATE_CALL(); UPDATE_PC_AND_RETURN(0);//I'llbeback... }
使用invokedynamic_cp_cache_entry_at获取常量池对象,然后检查是否已经解析过,如果没有就解析反之复用,然后设置方法字节码,留待后面解释执行。那么,重点是这个解析。我们对照着jvmspec来看。
根据jvm文档的描述,invokedynamic的操作数(operand)指向常量池一个动态调用点描述符(dynamiccallsitespecifier)。
动态调用点描述符是一个CONSTANT_InvokeDynamic_info结构体:
CONSTANT_InvokeDynamic_info{ u1tag; u2bootstrap_method_attr_index; u2name_and_type_index; }
•tag表示这个结构体的常量,不用管
•bootstrap_method_attr_index启动方法数组
•name_and_type_index一个名字+类型的描述字段,就像这样Objectp放到虚拟机里面表示是Ljava/lang/Object;p
然后启动方法数组结构是这样:
BootstrapMethods_attribute{ ... u2num_bootstrap_methods; { u2bootstrap_method_ref; u2num_bootstrap_arguments; u2bootstrap_arguments[num_boot] }bootstrap_methods[num_bootstrap_methods]; }
就是一个数组,每个元素是{指向MethodHandle的索引,启动方法参数个数,启动方法参数}
MethodlHandle是个非常重要的结构,指导了虚拟机对于这个启动方法的解析,先关注一下这个结构:
CONSTANT_MethodHandle_info{ u1tag;//表示该结构体的常量tag,可以忽略 u1reference_kind; u2reference_index; }
•reference_kind是[1,9]的数,它表示这个methodhandle的类型,这个字段和字节码的行为有关。
•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:#24invokestaticjava/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; Methodarguments: #25(LDerive;)V #26invokevirtualBase.encrypt:()V #25(LDerive;)V
得到的名字+描述符是:Encode.encode(),启动方法数组有一个元素,回忆下之前说的,这个元素构成如下:
{指向MethodHandle的索引,启动方法参数个数,启动方法参数}
这里得到的MethodHandle表示的是LambdaMetafactory.metafactory:
#24invokestaticjava/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
•#26invokevirtualBase.encrypt:()V
•#25(LDerive;)V
3.java.lang.invoke.LambdaMetafactory
先说说LambdaMetafactory有什么用。javadoc给出的解释是:
Facilitatesthecreationofsimple"functionobjects"thatimplementoneormoreinterfacesbydelegationtoaprovidedMethodHandle,afterappropriatetypeadaptationandpartialevaluationofarguments.Typicallyusedasabootstrapmethodforinvokedynamiccallsites,tosupportthelambdaexpressionandmethodreferenceexpressionfeaturesoftheJavaProgrammingLanguage.
WhenthetargetoftheCallSitereturnedfromthismethodisinvoked,theresultingfunctionobjectsareinstancesofaclasswhichimplementstheinterfacenamedbythereturntypeofinvokedType,declaresamethodwiththenamegivenbyinvokedNameandthesignaturegivenbysamMethodType.ItmayalsooverrideadditionalmethodsfromObject.
LambdaMetafactory方便我们创建简单的"函数对象",这些函数对象通过代理MethodHandle实现了一些接口。
当这个函数返回的CallSite被调用的时候,会产生一个类的实例,该类还实现了一些方法,具体由参数给出
将上面得到的MethodHandle写得更可读就是调用的这个方法:
publicstaticCallSiteLambdaMetafactory.metafactory(MethodHandles.Lookupcaller, StringinvokedName, MethodTypeinvokedType, MethodTypesamMethodType, MethodHandleimplMethod, MethodTypeinstantiatedMethodType);
六个参数,慢慢来。
3.1LambdaMetafactory.metafactory()调用前
要知道参数是什么意思,可以从它的调用者来管中窥豹:
staticCallSitemakeSite(MethodHandlebootstrapMethod, //Calleeinformation: Stringname,MethodTypetype, //ExtraargumentsforBSM,ifany: Objectinfo, //Callerinformation: Class>callerClass){ MethodHandles.Lookupcaller=IMPL_LOOKUP.in(callerClass); CallSitesite; try{ Objectbinding; info=maybeReBox(info); if(info==null){ binding=bootstrapMethod.invoke(caller,name,type); }elseif(!info.getClass().isArray()){ binding=bootstrapMethod.invoke(caller,name,type,info); }else{ Object[]argv=(Object[])info; maybeReBoxElements(argv); switch(argv.length){ ... case3: binding=bootstrapMethod.invoke(caller,name,type, argv[0],argv[1],argv[2]); break; ... } } //System.out.println("BSMfor"+name+type+"=>"+binding); if(bindinginstanceofCallSite){ site=(CallSite)binding; }else{ thrownewClassCastException("bootstrapmethodfailedtoproduceaCallSite"); } ... }catch(Throwableex){ ... } returnsite; }
对java.lang.invoke.LambdaMetafactory的调用是通过MethodHandle引发的,所以可能还需要补一下MethodHandle的用法,百度一搜一大堆,javadoc也给出了使用示例:
Strings; MethodTypemt;MethodHandlemh; MethodHandles.Lookuplookup=MethodHandles.lookup(); //mtis(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.Lookupcaller表示哪个类引发了调动
•StringinvokedName表示生成的类的方法名,对应例子的encode
•MethodTypeinvokedType表示CallSite的函数签名,其中参数类型表示捕获变量的类型,返回类型是类要实现的接口的名字,对应例子的()Encode,即要生成一个类,这个类没有捕获自由变量(所以参数类为空),然后这个类要实现Encode接口(返回类型为生成的类要实现的接口)
接下来
•MethodTypesamMethodType表示要实现的方法的函数签名和返回值,对于例子的#25(LDerive;)V,即实现方法带有一个形参,返回void
•MethodHandleimplMethod表示实现的方法里面应该调用的函数,对于例子的#26invokevirtualBase.encrypt:()V,表示调用Base的虚函数encrypt,返回void
•MethodTypeinstantiatedMethodType表示调用方法的运行时描述符,如果不是泛型就和samMethodType一样
3.2LambdaMetafactory.metafactory()调用
源码面前,不是了无秘密吗hhh,点进源码看看这个LambdaMetafactory到底做了什么:
*/ publicstaticCallSitemetafactory(MethodHandles.Lookupcaller, StringinvokedName, MethodTypeinvokedType, MethodTypesamMethodType, MethodHandleimplMethod, MethodTypeinstantiatedMethodType) throwsLambdaConversionException{ AbstractValidatingLambdaMetafactorymf; mf=newInnerClassLambdaMetafactory(caller,invokedType, invokedName,samMethodType, implMethod,instantiatedMethodType, false,EMPTY_CLASS_ARRAY,EMPTY_MT_ARRAY); mf.validateMetafactoryArgs(); returnmf.buildCallSite(); }
它什么也没做,做事的是InnerClassLambdaMetafactory.buildCallSite()创建的最后CallSite,那就进一步看看InnerClassLambdaMetafactory.buildCallSite():
@Override CallSitebuildCallSite()throwsLambdaConversionException{ //1.创建生成的类对象 finalClass>innerClass=spinInnerClass(); if(invokedType.parameterCount()==0){ //2.用反射获取构造函数 finalConstructor>[]ctrs=AccessController.doPrivileged( newPrivilegedAction[]>(){ @Override publicConstructor>[]run(){ Constructor>[]ctrs=innerClass.getDeclaredConstructors(); if(ctrs.length==1){ //Thelambdaimplementinginnerclassconstructorisprivate,set //itaccessible(byus)beforecreatingtheconstantsoleinstance ctrs[0].setAccessible(true); } returnctrs; } }); if(ctrs.length!=1){ thrownewLambdaConversionException("Expectedonelambdaconstructorfor" +innerClass.getCanonicalName()+",got"+ctrs.length); } try{ //3.创建实例 Objectinst=ctrs[0].newInstance(); //4.根据实例和samBase(接口类型)生成MethodHandle //5.生成ConstantCallSite returnnewConstantCallSite(MethodHandles.constant(samBase,inst)); } catch(ReflectiveOperationExceptione){ thrownewLambdaConversionException("Exceptioninstantiatinglambdaobject",e); } }else{ try{ UNSAFE.ensureClassInitialized(innerClass); returnnewConstantCallSite( MethodHandles.Lookup.IMPL_LOOKUP .findStatic(innerClass,NAME_FACTORY,invokedType)); } catch(ReflectiveOperationExceptione){ thrownewLambdaConversionException("Exceptionfindingconstructor",e); } } }
首先它生成一个.class文件,虚拟机默认不会输出,需要下面设置VMoption-Djdk.internal.lambda.dumpProxyClasses=.,Dump出虚拟机生成的类我得到的是:
importjava.lang.invoke.LambdaForm.Hidden; //$FF:syntheticclass finalclassMethodReference$$Lambda$1implementsEncode{ privateMethodReference$$Lambda$1(){ } @Hidden publicvoidencode(Derivevar1){ ((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:#24invokestaticjava/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; Methodarguments: #25(LDerive;)V #26invokevirtualBase.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,即newMethodReference$$Lambda$1
总结
以上所述是小编给大家介绍的Java中invokedynamic字节码指令问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!