時間:2023-07-12 15:42:01 | 來源:網(wǎng)站運營
時間:2023-07-12 15:42:01 來源:網(wǎng)站運營
【JVM源碼解析】虛擬機解釋執(zhí)行Java方法(上):本文由HeapDump性能社區(qū)首席講師鳩摩(馬智)授權(quán)整理發(fā)布
源代碼位置:openjdk/jdk/src/share/bin/java.c mainID = (*env)->GetStaticMethodID( env, mainClass, "main", "([Ljava/lang/String;)V");
env為JNIEnv*類型。調(diào)用JNIEnv類型中定義的GetStaticMethodID()函數(shù)獲取Java主類中main()方法的方法唯一ID,調(diào)用GetStaticMethodID()函數(shù)就是調(diào)用jni_GetStaticMethodID()函數(shù),此函數(shù)的實現(xiàn)如下:源代碼位置:openjdk/hotspot/src/share/vm/prims/jni.cpp JNI_ENTRY(jmethodID, jni_GetStaticMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig)) jmethodID ret = get_method_id(env, clazz, name, sig, true, thread); return ret;JNI_END static jmethodID get_method_id( JNIEnv *env, jclass clazz, const char *name_str, const char *sig, bool is_static, TRAPS){ const char *name_to_probe = (name_str == NULL) ? vmSymbols::object_initializer_name()->as_C_string() : name_str; TempNewSymbol name = SymbolTable::probe(name_to_probe, (int)strlen(name_to_probe)); TempNewSymbol signature = SymbolTable::probe(sig, (int)strlen(sig)); KlassHandle klass(THREAD,java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz))); // 保證java.lang.Class類已經(jīng)初始化完成 klass()->initialize(CHECK_NULL); Method* m; if ( name == vmSymbols::object_initializer_name() || 查找的是<init>方法 name == vmSymbols::class_initializer_name() ) { 查找的是<clinit>方法 // 因為要查找的是構(gòu)造函數(shù),構(gòu)造函數(shù)沒有繼承特性,所以當前類找不到時不向父類中繼續(xù)查找 if (klass->oop_is_instance()) { // find_method()函數(shù)不會向上查找 m = InstanceKlass::cast(klass())->find_method(name, signature); } else { m = NULL; } } else { // lookup_method()函數(shù)會向上查找 m = klass->lookup_method(name, signature); if (m == NULL && klass->oop_is_instance()) { m = InstanceKlass::cast(klass())->lookup_method_in_ordered_interfaces(name, signature); } } return m->jmethod_id();}
獲取Java類中main()方法的jmethod_id。源代碼位置:method.hpp// Get this method's jmethodID -- allocate if it doesn't existjmethodID jmethod_id() { methodHandle this_h(this); return InstanceKlass::get_jmethod_id(method_holder(), this_h);}
調(diào)用的InstanceKlass::get_jmethod_id()函數(shù)獲取唯一ID,關(guān)于如何獲取或生成ID的過程這里不再詳細介紹,有興趣的自行研究。mainArgs = CreateApplicationArgs(env, argv, argc); (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
通過調(diào)用CallStaticVoidMethod()函數(shù)來調(diào)用Java主類中的main()方法??刂茩?quán)轉(zhuǎn)移到Java主類中的main()方法之中。調(diào)用CallStaticVoidMethod()函數(shù)就是調(diào)用jni_CallStaticVoidMethod()函數(shù),此函數(shù)的實現(xiàn)如下:源代碼位置:openjdk/hotspot/src/share/vm/prims/jni.cpp JNI_ENTRY(void, jni_CallStaticVoidMethod(JNIEnv *env, jclass cls, jmethodID methodID, ...)) va_list args; va_start(args, methodID); JavaValue jvalue(T_VOID); JNI_ArgumentPusherVaArg ap(methodID, args); jni_invoke_static(env, &jvalue, NULL, JNI_STATIC, methodID, &ap, CHECK); va_end(args);JNI_END
將傳給Java方法的參數(shù)以C的可變長度參數(shù)傳入后,使用JNI_ArgumentPusherVaArg實例ap是將其封裝起來。JNI_ArgumentPusherVaArg類的繼承體系如下: JNI_ArgumentPusherVaArg->JNI_ArgumentPusher->SignatureIterator
調(diào)用的jni_invoke_static()函數(shù)的實現(xiàn)如下:// 通過jni的方式調(diào)用Java靜態(tài)方法static void jni_invoke_static( JNIEnv *env, JavaValue* result, jobject receiver, JNICallType call_type, jmethodID method_id, JNI_ArgumentPusher *args, TRAPS){ Method* m = Method::resolve_jmethod_id(method_id); methodHandle method(THREAD, m); ResourceMark rm(THREAD); int number_of_parameters = method->size_of_parameters(); // 這里進一步將要傳給Java的參數(shù)轉(zhuǎn)換為JavaCallArguments對象傳下去 JavaCallArguments java_args(number_of_parameters); args->set_java_argument_object(&java_args); // Fill out(填,填寫) JavaCallArguments object Fingerprinter fp = Fingerprinter(method); uint64_t x = fp.fingerprint(); args->iterate(x); // Initialize result type BasicType bt = args->get_ret_type(); result->set_type(bt); // Invoke the method. Result is returned as oop. JavaCalls::call(result, method, &java_args, CHECK); // Convert result if ( result->get_type() == T_OBJECT || result->get_type() == T_ARRAY ) { oop tmp = (oop) result->get_jobject(); jobject jobj = JNIHandles::make_local(env,tmp); result->set_jobject(jobj); }}
通過JavaCalls::call()函數(shù)來調(diào)用Java主類的main()方法。關(guān)于JavaCalls::call()函數(shù)大家應該不會陌生,這個函數(shù)是怎么建立Java棧幀以及找到Java方法入口在之前已經(jīng)詳細介紹過,這里不再介紹。rbx:Method*ecx:invocation counterr13:bcp(byte code pointer)rdx:ConstantPool* 常量池的地址r14:本地變量表第1個參數(shù)的地址
現(xiàn)在我們舉一個例子,來完整的走一下解釋執(zhí)行的過程。這個例子如下:package com.classloading; public class Test { public static void main(String[] args) { int i = 0; i = i++; }}
通過javap -verbose Test.class命令反編譯后的字節(jié)碼文件內(nèi)容如下: Constant pool: #1 = Methodref #3.#12 // java/lang/Object."<init>":()V #2 = Class #13 // com/classloading/Test #3 = Class #14 // java/lang/Object #4 = Utf8 <init> #5 = Utf8 ()V #6 = Utf8 Code #7 = Utf8 LineNumberTable #8 = Utf8 main #9 = Utf8 ([Ljava/lang/String;)V #10 = Utf8 SourceFile #11 = Utf8 Test.java #12 = NameAndType #4:#5 // "<init>":()V #13 = Utf8 com/classloading/Test #14 = Utf8 java/lang/Object{ ... public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=2, args_size=1 0: iconst_0 1: istore_1 2: return}
如上實例對應的棧幀狀態(tài)如下圖所示。// 在generate_fixed_frame()方法中已經(jīng)讓%r13存儲了bcpmovzbl 0x0(%r13),%ebx // %ebx中存儲的是字節(jié)碼的操作碼 // $0x7ffff73ba4a0這個地址指向的是對應state狀態(tài)下的一維數(shù)組,長度為256movabs $0x7ffff73ba4a0,%r10 // 注意%r10中存儲的是常量,根據(jù)計算公式%r10+%rbx*8來獲取指向存儲入口地址的地址,// 通過*(%r10+%rbx*8)獲取到入口地址,然后跳轉(zhuǎn)到入口地址執(zhí)行jmpq *(%r10,%rbx,8)
注意如上的$0x7ffff73ba4a0這個常量值已經(jīng)表示了棧頂緩存狀態(tài)為vtos下的一維數(shù)組首地址。而在首次進行方法的字節(jié)碼分派時,通過0x0(%r13)即可取出字節(jié)碼對應的Opcode,使用這個Opcode可定位到iconst_0的入口地址。... // vtos入口mov $0x1,%eax ...// iconst_0對應的匯編代碼xor %eax,%eax
匯編指令足夠簡單,最后將值存儲到了%eax中,所以也就是棧頂緩存的出口狀態(tài)為itos。movzbl 0x1(%r13),%ebx // %ebx中存儲的是字節(jié)碼的操作碼 movabs itos對應的一維數(shù)組的首地址,%r10 jmpq *(%r10,%rbx,8)
需要注意的是,如果要讓%ebx中存儲istore_1的Opcode,則%r13需要加上iconst_0指令的長度,即1。由于iconst_0執(zhí)行后的出口棧頂緩存為itos,所以要找到入口狀態(tài)為itos,而Opcode為istore_1的機器指令片段執(zhí)行。指令片段如下:mov %eax,-0x8(%r14)
代碼將棧頂?shù)闹?eax存儲到本地變量表下標索引為1的位置處。通過%r14很容易定位到本地變量表的位置,執(zhí)行完成后的棧狀態(tài)如下圖所示。// 將JavaThread::do_not_unlock_if_synchronized屬性存儲到%dl中0x00007fffe101b770: mov 0x2ad(%r15),%dl// 重置JavaThread::do_not_unlock_if_synchronized屬性值為false0x00007fffe101b777: movb $0x0,0x2ad(%r15) // 將Method*加載到%rbx中0x00007fffe101b77f: mov -0x18(%rbp),%rbx// 將Method::_access_flags加載到%ecx中0x00007fffe101b783: mov 0x28(%rbx),%ecx// 檢查Method::flags是否包含JVM_ACC_SYNCHRONIZED0x00007fffe101b786: test $0x20,%ecx// 如果方法不是同步方法,跳轉(zhuǎn)到----unlocked----0x00007fffe101b78c: je 0x00007fffe101b970
main()方法為非同步方法,所以跳轉(zhuǎn)到unlocked處執(zhí)行,在unlocked處執(zhí)行的邏輯中會執(zhí)行一些釋放鎖的邏輯,對于我們本實例來說這不重要,我們直接看退棧的操作,如下:// 將-0x8(%rbp)處保存的old stack pointer(saved rsp)取出來放到%rbx中0x00007fffe101bac7: mov -0x8(%rbp),%rbx // 移除棧幀// leave指令相當于:// mov %rbp, %rsp// pop %rbp0x00007fffe101bacb: leaveq // 將返回地址彈出到%r13中0x00007fffe101bacc: pop %r13// 設(shè)置%rsp為調(diào)用者的棧頂值0x00007fffe101bace: mov %rbx,%rsp0x00007fffe101bad1: jmpq *%r13
這個匯編不難,這里不再繼續(xù)介紹。退棧后的棧狀態(tài)如下圖所示。def(Bytecodes::_invokevirtual , ubcp|disp|clvm|____, vtos, vtos, invokevirtual , f2_byte );
生成函數(shù)為invokevirtual,傳遞的參數(shù)為f2_byte,也就是2,如果為2時,ConstantPoolCacheEntry::indices中取[b2,b1,original constant pool index]中的b2部分。調(diào)用的TemplateTable::invokevirtual()函數(shù)的實現(xiàn)如下:void TemplateTable::invokevirtual(int byte_no) { prepare_invoke(byte_no, rbx, // method or vtable index noreg, // unused itable index rcx, // recv rdx); // flags // rbx: index // rcx: receiver // rdx: flags invokevirtual_helper(rbx, rcx, rdx);}
先調(diào)用prepare_invoke()函數(shù),后調(diào)用invokevirtual_helper()函數(shù)來生成invokevirtual字節(jié)碼指令對應的匯編代碼(其實是生成機器指令,然后反編譯對應的匯編代碼,在后面我們就直接表述為匯編代碼,讀者要知道)。0x00007fffe1021f90: mov %r13,-0x38(%rbp) // 將bcp保存到棧中// invokevirtual x中取出x,也就是常量池索引存儲到%edx,// 其實這里已經(jīng)是ConstantPoolCacheEntry的index,因為在類的連接// 階段會對方法中特定的一些字節(jié)碼指令進行重寫0x00007fffe1021f94: movzwl 0x1(%r13),%edx // 將ConstantPoolCache的首地址存儲到%rcx0x00007fffe1021f99: mov -0x28(%rbp),%rcx // 左移2位,因為%edx中存儲的是ConstantPoolCacheEntry索引,左移2位是因為// ConstantPoolCacheEntry占用4個字0x00007fffe1021f9d: shl $0x2,%edx // 計算%rcx+%rdx*8+0x10,獲取ConstantPoolCacheEntry[_indices,_f1,_f2,_flags]中的_indices// 因為ConstantPoolCache的大小為0x16字節(jié),%rcx+0x10定位// 到第一個ConstantPoolCacheEntry的位置// %rdx*8算出來的是相對于第一個ConstantPoolCacheEntry的字節(jié)偏移0x00007fffe1021fa0: mov 0x10(%rcx,%rdx,8),%ebx // 獲取ConstantPoolCacheEntry中indices[b2,b1,constant pool index]中的b20x00007fffe1021fa4: shr $0x18,%ebx // 取出indices中含有的b2,即bytecode存儲到%ebx中0x00007fffe1021fa7: and $0xff,%ebx // 查看182的bytecode是否已經(jīng)連接 0x00007fffe1021fad: cmp $0xb6,%ebx // 如果連接就進行跳轉(zhuǎn),跳轉(zhuǎn)到resolved 0x00007fffe1021fb3: je 0x00007fffe1022052
主要查看字節(jié)碼是否已經(jīng)連接,如果沒有連接則需要連接,如果已經(jīng)進行了連接,則跳轉(zhuǎn)到resolved直接執(zhí)行方法調(diào)用操作。// 調(diào)用InterpreterRuntime::resolve_invoke()函數(shù),因為指令還沒有連接// 將bytecode為182的指令移動到%ebx中0x00007fffe1021fb9: mov $0xb6,%ebx // 通過調(diào)用MacroAssembler::call_VM()函數(shù)來調(diào)用// InterpreterRuntime::resolve_invoke(JavaThread* thread, Bytecodes::Code bytecode)函數(shù)// 進行方法連接0x00007fffe1021fbe: callq 0x00007fffe1021fc8 0x00007fffe1021fc3: jmpq 0x00007fffe1022046 // 跳轉(zhuǎn)到----E----// 準備第2個參數(shù),也就是bytecode0x00007fffe1021fc8: mov %rbx,%rsi 0x00007fffe1021fcb: lea 0x8(%rsp),%rax0x00007fffe1021fd0: mov %r13,-0x38(%rbp)0x00007fffe1021fd4: mov %r15,%rdi0x00007fffe1021fd7: mov %rbp,0x200(%r15)0x00007fffe1021fde: mov %rax,0x1f0(%r15)0x00007fffe1021fe5: test $0xf,%esp0x00007fffe1021feb: je 0x00007fffe10220030x00007fffe1021ff1: sub $0x8,%rsp0x00007fffe1021ff5: callq 0x00007ffff66ac5280x00007fffe1021ffa: add $0x8,%rsp0x00007fffe1021ffe: jmpq 0x00007fffe10220080x00007fffe1022003: callq 0x00007ffff66ac5280x00007fffe1022008: movabs $0x0,%r100x00007fffe1022012: mov %r10,0x1f0(%r15)0x00007fffe1022019: movabs $0x0,%r100x00007fffe1022023: mov %r10,0x200(%r15)0x00007fffe102202a: cmpq $0x0,0x8(%r15)0x00007fffe1022032: je 0x00007fffe102203d0x00007fffe1022038: jmpq 0x00007fffe10004200x00007fffe102203d: mov -0x38(%rbp),%r130x00007fffe1022041: mov -0x30(%rbp),%r140x00007fffe1022045: retq // 結(jié)束MacroAssembler::call_VM()函數(shù)的調(diào)用// **** E ****// 將invokevirtual x中的x加載到%edx中,也就是ConstantPoolCacheEntry的索引0x00007fffe1022046: movzwl 0x1(%r13),%edx // 將ConstantPoolCache的首地址存儲到%rcx中 0x00007fffe102204b: mov -0x28(%rbp),%rcx // %edx中存儲的是ConstantPoolCacheEntry index,轉(zhuǎn)換為字偏移0x00007fffe102204f: shl $0x2,%edx
方法連接的邏輯和之前介紹的字段的連接邏輯類似,都是完善ConstantPoolCache中對應的ConstantPoolCacheEntry添加相關(guān)信息。// **** resolved ****// resolved的定義點,到這里說明invokevirtual字節(jié)碼已經(jīng)連接// 獲取ConstantPoolCacheEntry::_f2,這個字段只對virtual有意義// 在計算時,因為ConstantPoolCacheEntry在ConstantPoolCache之后保存,// 所以ConstantPoolCache為0x10,而// _f2還要偏移0x10,這樣總偏移就是0x20// ConstantPoolCacheEntry::_f2存儲到%rbx0x00007fffe1022052: mov 0x20(%rcx,%rdx,8),%rbx // ConstantPoolCacheEntry::_flags存儲到%edx0x00007fffe1022057: mov 0x28(%rcx,%rdx,8),%edx // 將flags移動到ecx中0x00007fffe102205b: mov %edx,%ecx // 從flags中取出參數(shù)大小 0x00007fffe102205d: and $0xff,%ecx // 獲取到recv,%rcx中保存的是參數(shù)大小,最終計算參數(shù)所需要的大小為%rsp+%rcx*8-0x8,// flags中的參數(shù)大小對實例方法來說,已經(jīng)包括了recv的大小// 如調(diào)用實例方法的第一個參數(shù)是this(recv)0x00007fffe1022063: mov -0x8(%rsp,%rcx,8),%rcx // recv保存到%rcx // 將flags存儲到r13中0x00007fffe1022068: mov %edx,%r13d // 從flags中獲取return type,也就是從_flags的高4位保存的TosState0x00007fffe102206b: shr $0x1c,%edx // 將TemplateInterpreter::invoke_return_entry地址存儲到%r100x00007fffe102206e: movabs $0x7ffff73b6380,%r10 // %rdx保存的是return type,計算返回地址// 因為TemplateInterpreter::invoke_return_entry是數(shù)組,// 所以要找到對應return type的入口地址0x00007fffe1022078: mov (%r10,%rdx,8),%rdx // 向棧中壓入返回地址0x00007fffe102207c: push %rdx // 還原ConstantPoolCacheEntry::_flags 0x00007fffe102207d: mov %r13d,%edx // 還原bcp0x00007fffe1022080: mov -0x38(%rbp),%r13
TemplateInterpreter::invoke_return_entry保存了一段例程的入口,這段例程在后面會詳細介紹。rbx: 存儲的是ConstantPoolCacheEntry::_f2屬性的值rcx: 就是調(diào)用實例方法時的第一個參數(shù)thisrdx: 存儲的是ConstantPoolCacheEntry::_flags屬性的值
棧的狀態(tài)如下圖所示。// flags存儲到%eax0x00007fffe1022084: mov %edx,%eax // 測試調(diào)用的方法是否為final 0x00007fffe1022086: and $0x100000,%eax // 如果不為final就直接跳轉(zhuǎn)到----notFinal---- 0x00007fffe102208c: je 0x00007fffe10220c0 // 通過(%rcx)來獲取receiver的值,如果%rcx為空,則會引起OS異常0x00007fffe1022092: cmp (%rcx),%rax // 省略統(tǒng)計相關(guān)代碼部分// 設(shè)置調(diào)用者棧頂并保存0x00007fffe10220b4: lea 0x8(%rsp),%r130x00007fffe10220b9: mov %r13,-0x10(%rbp)// 跳轉(zhuǎn)到Method::_from_interpretered_entry入口去執(zhí)行0x00007fffe10220bd: jmpq *0x58(%rbx)
對于final方法來說,其實沒有動態(tài)分派,所以也不需要通過vtable進行目標查找。調(diào)用時的棧如下圖所示。// **** notFinal ****// invokevirtual指令調(diào)用的如果是非final方法,直接跳轉(zhuǎn)到這里// %rcx中存儲的是receiver,用oop來表示。通過oop獲取Klass0x00007fffe10220c0: mov 0x8(%rcx),%eax // 調(diào)用MacroAssembler::decode_klass__not_null()函數(shù)生成下面的一個匯編代碼0x00007fffe10220c3: shl $0x3,%rax // LogKlassAlignmentInBytes=0x03// 省略統(tǒng)計相關(guān)代碼部分// %rax中存儲的是recv_klass// %rbx中存儲的是vtable_index,// 而0x1b8為InstanceKlass::vtable_start_offset()*wordSize+vtableEntry::method_offset_in_bytes(),// 其實就是通過動態(tài)分派找到需要調(diào)用的Method*并存儲到%rbx中0x00007fffe1022169: mov 0x1b8(%rax,%rbx,8),%rbx// 設(shè)置調(diào)用者的棧頂?shù)刂凡⒈4?x00007fffe1022171: lea 0x8(%rsp),%r130x00007fffe1022176: mov %r13,-0x10(%rbp)// 跳轉(zhuǎn)到Method::_from_interpreted_entry處執(zhí)行0x00007fffe102217a: jmpq *0x58(%rbx)
理解如上代碼時需要知道vtable方法分派以及vtable在InstanceKlass中的布局,這在《深入剖析Java虛擬機:源碼剖析與實例詳解》一書中詳細介紹過,這里不再介紹?! ?br>void MacroAssembler::lookup_virtual_method(Register recv_klass, RegisterOrConstant vtable_index, Register method_result) { const int base = InstanceKlass::vtable_start_offset() * wordSize; Address vtable_entry_addr( recv_klass, vtable_index, Address::times_ptr, base + vtableEntry::method_offset_in_bytes()); movptr(method_result, vtable_entry_addr);}
其中的vtable_index取的就是ConstantPoolCacheEntry::_f2屬性的值。Handle receiver(thread, NULL);if (bytecode == Bytecodes::_invokevirtual || bytecode == Bytecodes::_invokeinterface) { ResourceMark rm(thread); // 調(diào)用method()函數(shù)從當前的棧幀中獲取到需要執(zhí)行的方法 Method* m1 = method(thread); methodHandle m (thread, m1); // 調(diào)用bci()函數(shù)從當前的棧幀中獲取需要執(zhí)行的方法的字節(jié)碼索引 int i1 = bci(thread); Bytecode_invoke call(m, i1); // 當前需要執(zhí)行的方法的簽名 Symbol* signature = call.signature(); frame fm = thread->last_frame(); oop x = fm.interpreter_callee_receiver(signature); receiver = Handle(thread,x);}
當字節(jié)碼為invokevirtual或invokeinterface這樣的動態(tài)分派字節(jié)碼時,執(zhí)行如上的邏輯。獲取到了receiver變量的值。接著看實現(xiàn),如下:CallInfo info;constantPoolHandle pool(thread, method(thread)->constants()); { JvmtiHideSingleStepping jhss(thread); int cpcacheindex = get_index_u2_cpcache(thread, bytecode); LinkResolver::resolve_invoke(info, receiver, pool,cpcacheindex, bytecode, CHECK); ...} // 如果已經(jīng)向ConstantPoolCacheEntry中更新了調(diào)用的相關(guān)信息則直接返回if (already_resolved(thread)) return;
根據(jù)存儲在當前棧中的bcp來獲取字節(jié)碼指令的操作數(shù),這個操作數(shù)通常就是常量池緩存項索引。然后調(diào)用LinkResolver::resolve_invoke()函數(shù)進行方法連接。 這個函數(shù)會間接調(diào)用LinkResolver::resolve_invokevirtual()函數(shù),實現(xiàn)如下:void LinkResolver::resolve_invokevirtual( CallInfo& result, Handle recv, constantPoolHandle pool, int index, TRAPS){ KlassHandle resolved_klass; Symbol* method_name = NULL; Symbol* method_signature = NULL; KlassHandle current_klass; resolve_pool(resolved_klass, method_name, method_signature, current_klass, pool, index, CHECK); KlassHandle recvrKlass(THREAD, recv.is_null() ? (Klass*)NULL : recv->klass()); resolve_virtual_call(result, recv, recvrKlass, resolved_klass, method_name, method_signature, current_klass, true, true, CHECK);}
其中會調(diào)用resolve_pool()和resolve_vritual_call()函數(shù)分別連接常量池和方法調(diào)用指令。調(diào)用會涉及到的相關(guān)函數(shù)大概如下圖所示。void LinkResolver::resolve_pool( KlassHandle& resolved_klass, Symbol*& method_name, Symbol*& method_signature, KlassHandle& current_klass, constantPoolHandle pool, int index, TRAPS) { resolve_klass(resolved_klass, pool, index, CHECK); method_name = pool->name_ref_at(index); method_signature = pool->signature_ref_at(index); current_klass = KlassHandle(THREAD, pool->pool_holder());}
其中的index為常量池緩存項的索引。resolved_klass參數(shù)表示需要進行解析的類(解析是在類生成周期中連接相關(guān)的部分,所以我們之前有時候會稱為連接,其實具體來說是解析的意思),而current_klass為當前擁有常量池的類,由于傳遞參數(shù)時是C++的引用傳遞,所以同值會直接改變變量的值,調(diào)用者中的值也會隨著改變。void LinkResolver::resolve_klass( KlassHandle& result, constantPoolHandle pool, int index, TRAPS) { Klass* result_oop = pool->klass_ref_at(index, CHECK); // 通過引用進行傳遞 result = KlassHandle(THREAD, result_oop);} Klass* ConstantPool::klass_ref_at(int which, TRAPS) { int x = klass_ref_index_at(which); return klass_at(x, CHECK_NULL);} int klass_ref_index_at(int which) { return impl_klass_ref_index_at(which, false);}
調(diào)用的impl_klass_ref_index_at()函數(shù)的實現(xiàn)如下: int ConstantPool::impl_klass_ref_index_at(int which, bool uncached) { int i = which; if (!uncached && cache() != NULL) { // 從which對應的ConstantPoolCacheEntry項中獲取ConstantPoolIndex i = remap_instruction_operand_from_cache(which); } assert(tag_at(i).is_field_or_method(), "Corrupted constant pool"); // 獲取 jint ref_index = *int_at_addr(i); // 獲取低16位,那就是class_index return extract_low_short_from_int(ref_index);}
根據(jù)斷言可知,在原常量池索引的i處的項肯定為JVM_CONSTANT_Fieldref、JVM_CONSTANT_Methodref或JVM_CONSTANT_InterfaceMethodref,這幾項的格式如下:CONSTANT_Fieldref_info{ u1 tag; u2 class_index; u2 name_and_type_index; // 必須是字段描述符} CONSTANT_InterfaceMethodref_info{ u1 tag; u2 class_index; // 必須是接口 u2 name_and_type_index; // 必須是方法描述符} CONSTANT_Methodref_info{ u1 tag; u2 class_index; // 必須是類 u2 name_and_type_index; // 必須是方法描述符}
3項的格式都一樣,其中的class_index索引處的項必須為CONSTANT_Class_info結(jié)構(gòu),表示一個類或接口,當前類字段或方法是這個類或接口的成員。name_and_type_index索引處必須為CONSTANT_NameAndType_info項?! ?br>Klass* klass_at(int which, TRAPS) { constantPoolHandle h_this(THREAD, this); return klass_at_impl(h_this, which, CHECK_NULL);}
調(diào)用的klass_at_impl()函數(shù)的實現(xiàn)如下:Klass* ConstantPool::klass_at_impl( constantPoolHandle this_oop, int which, TRAPS) { CPSlot entry = this_oop->slot_at(which); if (entry.is_resolved()) { // 已經(jīng)進行了連接 return entry.get_klass(); } bool do_resolve = false; bool in_error = false; Handle mirror_handle; Symbol* name = NULL; Handle loader; { MonitorLockerEx ml(this_oop->lock()); if (this_oop->tag_at(which).is_unresolved_klass()) { if (this_oop->tag_at(which).is_unresolved_klass_in_error()) { in_error = true; } else { do_resolve = true; name = this_oop->unresolved_klass_at(which); loader = Handle(THREAD, this_oop->pool_holder()->class_loader()); } } } // unlocking constantPool // 省略當in_error變量的值為true時的處理邏輯 if (do_resolve) { oop protection_domain = this_oop->pool_holder()->protection_domain(); Handle h_prot (THREAD, protection_domain); Klass* k_oop = SystemDictionary::resolve_or_fail(name, loader, h_prot, true, THREAD); KlassHandle k; if (!HAS_PENDING_EXCEPTION) { k = KlassHandle(THREAD, k_oop); mirror_handle = Handle(THREAD, k_oop->java_mirror()); } if (HAS_PENDING_EXCEPTION) { ... return 0; } if (TraceClassResolution && !k()->oop_is_array()) { ... } else { MonitorLockerEx ml(this_oop->lock()); do_resolve = this_oop->tag_at(which).is_unresolved_klass(); if (do_resolve) { ClassLoaderData* this_key = this_oop->pool_holder()->class_loader_data(); this_key->record_dependency(k(), CHECK_NULL); // Can throw OOM this_oop->klass_at_put(which, k()); // 注意這里會更新常量池中存儲的內(nèi)容,這樣就表示類已經(jīng)解析完成,下次就不需要重復解析了 } } } entry = this_oop->resolved_klass_at(which); assert(entry.is_resolved() && entry.get_klass()->is_klass(), "must be resolved at this point"); return entry.get_klass();}
函數(shù)首先調(diào)用slot_at()函數(shù)獲取常量池中一個slot中存儲的值,然后通過CPSlot來表示這個slot,這個slot中可能存儲的值有2個,分別為指向Symbol實例(因為類名用CONSTANT_Utf8_info項表示,在虛擬機內(nèi)部統(tǒng)一使用Symbol對象表示字符串)的指針和指向Klass實例的指針,如果類已經(jīng)解釋,那么指針表示的地址的最后一位為0,如果還沒有被解析,那么地址的最后一位為1。CONSTANT_NameAndType_info{ u1 tag; u2 name_index; u2 descriptor index;}
獲取邏輯就是先根據(jù)常量池緩存項的索引找到原常量池項的索引,然后查找到CONSTANT_NameAndType_info后,獲取到方法名稱和簽名的索引,進而獲取到被調(diào)用的目標方法的名稱和簽名。這些信息將在接下來調(diào)用的resolve_virtual_call()函數(shù)中使用。void LinkResolver::resolve_virtual_call( CallInfo& result, Handle recv, KlassHandle receiver_klass, KlassHandle resolved_klass, Symbol* method_name, Symbol* method_signature, KlassHandle current_klass, bool check_access, bool check_null_and_abstract, TRAPS) { methodHandle resolved_method; linktime_resolve_virtual_method(resolved_method, resolved_klass, method_name, method_signature, current_klass, check_access, CHECK); runtime_resolve_virtual_method(result, resolved_method, resolved_klass, recv, receiver_klass, check_null_and_abstract, CHECK);}
首先調(diào)用LinkResolver::linktime_resolve_virtual_method()函數(shù),這個函數(shù)會調(diào)用如下函數(shù):void LinkResolver::resolve_method( methodHandle& resolved_method, KlassHandle resolved_klass, Symbol* method_name, Symbol* method_signature, KlassHandle current_klass, bool check_access, bool require_methodref, TRAPS) { // 從解析的類和其父類中查找方法 lookup_method_in_klasses(resolved_method, resolved_klass, method_name, method_signature, true, false, CHECK); // 沒有在解析類的繼承體系中查找到方法 if (resolved_method.is_null()) { // 從解析類實現(xiàn)的所有接口(包括間接實現(xiàn)的接口)中查找方法 lookup_method_in_interfaces(resolved_method, resolved_klass, method_name, method_signature, CHECK); // ... if (resolved_method.is_null()) { // 沒有找到對應的方法 ... } } // ...}
如上函數(shù)中最主要的就是根據(jù)method_name和method_signature從resolved_klass類中找到合適的方法,如果找到就賦值給resolved_method變量。void LinkResolver::runtime_resolve_virtual_method( CallInfo& result, methodHandle resolved_method, KlassHandle resolved_klass, Handle recv, KlassHandle recv_klass, bool check_null_and_abstract, TRAPS) { int vtable_index = Method::invalid_vtable_index; methodHandle selected_method; // 當方法定義在接口中時,表示是miranda方法 if (resolved_method->method_holder()->is_interface()) { vtable_index = vtable_index_of_interface_method(resolved_klass,resolved_method); InstanceKlass* inst = InstanceKlass::cast(recv_klass()); selected_method = methodHandle(THREAD, inst->method_at_vtable(vtable_index)); } else { // 如果走如下的代碼邏輯,則表示resolved_method不是miranda方法,需要動態(tài)分派且肯定有正確的vtable索引 vtable_index = resolved_method->vtable_index(); // 有些方法雖然看起來需要動態(tài)分派,但是如果這個方法有final關(guān)鍵字時,可進行靜態(tài)綁定,所以直接調(diào)用即可 // final方法其實不會放到vtable中,除非final方法覆寫了父類中的方法 if (vtable_index == Method::nonvirtual_vtable_index) { selected_method = resolved_method; } else { // 根據(jù)vtable和vtable_index以及inst進行方法的動態(tài)分派 InstanceKlass* inst = (InstanceKlass*)recv_klass(); selected_method = methodHandle(THREAD, inst->method_at_vtable(vtable_index)); } } // setup result resolve的類型為CallInfo,為CallInfo設(shè)置了連接后的相關(guān)信息 result.set_virtual(resolved_klass, recv_klass, resolved_method, selected_method, vtable_index, CHECK);}
當為miranda方法時,調(diào)用 LinkResolver::vtable_index_of_interface_method()函數(shù)查找;當為final方法時,因為final方法不可能被子類覆寫,所以resolved_method就是目標調(diào)用方法;除去前面的2種情況后,剩下的方法就需要結(jié)合vtable和vtable_index進行動態(tài)分派了。switch (info.call_kind()) { case CallInfo::direct_call: // 直接調(diào)用 cache_entry(thread)->set_direct_call( bytecode, info.resolved_method()); break; case CallInfo::vtable_call: // vtable分派 cache_entry(thread)->set_vtable_call( bytecode, info.resolved_method(), info.vtable_index()); break; case CallInfo::itable_call: // itable分派 cache_entry(thread)->set_itable_call( bytecode, info.resolved_method(), info.itable_index()); break; default: ShouldNotReachHere();}
無論直接調(diào)用,還是vtable和itable動態(tài)分派,都會在方法解析完成后將相關(guān)的信息存儲到常量池緩存項中。調(diào)用cache_entry()函數(shù)獲取對應的ConstantPoolCacheEntry項,然后調(diào)用set_vtable_call()函數(shù),此函數(shù)會調(diào)用如下函數(shù)更新ConstantPoolCacheEntry項中的信息,如下:void ConstantPoolCacheEntry::set_direct_or_vtable_call( Bytecodes::Code invoke_code, methodHandle method, int vtable_index) { bool is_vtable_call = (vtable_index >= 0); // FIXME: split this method on this boolean int byte_no = -1; bool change_to_virtual = false; switch (invoke_code) { case Bytecodes::_invokeinterface: change_to_virtual = true; // ... // 可以看到,通過_invokevirtual指令時,并不一定都是動態(tài)分發(fā),也有可能是靜態(tài)綁定 case Bytecodes::_invokevirtual: // 當前已經(jīng)在ConstantPoolCacheEntry類中了 { if (!is_vtable_call) { assert(method->can_be_statically_bound(), ""); // set_f2_as_vfinal_method checks if is_vfinal flag is true. set_method_flags(as_TosState(method->result_type()), ( 1 << is_vfinal_shift) | ((method->is_final_method() ? 1 : 0) << is_final_shift) | ((change_to_virtual ? 1 : 0) << is_forced_virtual_shift), // 在接口中調(diào)用Object中定義的方法 method()->size_of_parameters()); set_f2_as_vfinal_method(method()); } else { // 執(zhí)行這里的邏輯時,表示方法是非靜態(tài)綁定的非final方法,需要動態(tài)分派,則vtable_index的值肯定大于等于0 set_method_flags(as_TosState(method->result_type()), ((change_to_virtual ? 1 : 0) << is_forced_virtual_shift), method()->size_of_parameters()); // 對于動態(tài)分發(fā)來說,ConstantPoolCacheEntry::_f2中保存的是vtable_index set_f2(vtable_index); } byte_no = 2; break; } // ... } if (byte_no == 1) { // invoke_code為非invokevirtual和非invokeinterface字節(jié)碼指令 set_bytecode_1(invoke_code); } else if (byte_no == 2) { if (change_to_virtual) { if (method->is_public()) set_bytecode_1(invoke_code); } else { assert(invoke_code == Bytecodes::_invokevirtual, ""); } // set up for invokevirtual, even if linking for invokeinterface also: set_bytecode_2(Bytecodes::_invokevirtual); } }
連接完成后ConstantPoolCacheEntry中的各個項如下圖所示。def(Bytecodes::_invokeinterface , ubcp|disp|clvm|____, vtos, vtos, invokeinterface , f1_byte );
可以看到指令的生成函數(shù)為TemplateTable::invokeinterface(),在這個函數(shù)中首先會調(diào)用TemplateTable::prepare_invoke()函數(shù),TemplateTable::prepare_invoke()函數(shù)生成的匯編代碼如下:0x00007fffe1022610: mov %r13,-0x38(%rbp)0x00007fffe1022614: movzwl 0x1(%r13),%edx0x00007fffe1022619: mov -0x28(%rbp),%rcx0x00007fffe102261d: shl $0x2,%edx// 獲取ConstantPoolCacheEntry[_indices,_f1,_f2,_flags]中的_indices0x00007fffe1022620: mov 0x10(%rcx,%rdx,8),%ebx // 獲取ConstantPoolCacheEntry中indices[b2,b1,constant pool index]中的b1// 如果已經(jīng)連接,那這個b1應該等于185,也就是invokeinterface指令的操作碼0x00007fffe1022624: shr $0x10,%ebx0x00007fffe1022627: and $0xff,%ebx0x00007fffe102262d: cmp $0xb9,%ebx// 如果invokeinterface已經(jīng)連接就跳轉(zhuǎn)到----resolved----0x00007fffe1022633: je 0x00007fffe10226d2
匯編代碼的判斷邏輯與invokevirutal一致,這里不在過多解釋。// 執(zhí)行如下匯編代碼時,表示invokeinterface指令還沒有連接,也就是ConstantPoolCacheEntry中// 還沒有保存調(diào)用相關(guān)的信息 // 通過調(diào)用call_VM()函數(shù)生成如下匯編,通過這些匯編// 調(diào)用InterpreterRuntime::resolve_invoke()函數(shù)// 將bytecode存儲到%ebx中0x00007fffe1022639: mov $0xb9,%ebx // 通過MacroAssembler::call_VM()來調(diào)用InterpreterRuntime::resolve_invoke()0x00007fffe102263e: callq 0x00007fffe1022648 0x00007fffe1022643: jmpq 0x00007fffe10226c60x00007fffe1022648: mov %rbx,%rsi0x00007fffe102264b: lea 0x8(%rsp),%rax0x00007fffe1022650: mov %r13,-0x38(%rbp)0x00007fffe1022654: mov %r15,%rdi0x00007fffe1022657: mov %rbp,0x200(%r15)0x00007fffe102265e: mov %rax,0x1f0(%r15)0x00007fffe1022665: test $0xf,%esp0x00007fffe102266b: je 0x00007fffe10226830x00007fffe1022671: sub $0x8,%rsp0x00007fffe1022675: callq 0x00007ffff66ae13a0x00007fffe102267a: add $0x8,%rsp0x00007fffe102267e: jmpq 0x00007fffe10226880x00007fffe1022683: callq 0x00007ffff66ae13a0x00007fffe1022688: movabs $0x0,%r100x00007fffe1022692: mov %r10,0x1f0(%r15)0x00007fffe1022699: movabs $0x0,%r100x00007fffe10226a3: mov %r10,0x200(%r15)0x00007fffe10226aa: cmpq $0x0,0x8(%r15)0x00007fffe10226b2: je 0x00007fffe10226bd0x00007fffe10226b8: jmpq 0x00007fffe10004200x00007fffe10226bd: mov -0x38(%rbp),%r130x00007fffe10226c1: mov -0x30(%rbp),%r140x00007fffe10226c5: retq // 結(jié)束MacroAssembler::call_VM()函數(shù)// 將invokeinterface x中的x加載到%edx中0x00007fffe10226c6: movzwl 0x1(%r13),%edx// 將ConstantPoolCache的首地址存儲到%rcx中0x00007fffe10226cb: mov -0x28(%rbp),%rcx// %edx中存儲的是ConstantPoolCacheEntry項的索引,轉(zhuǎn)換為字節(jié)// 偏移,因為一個ConstantPoolCacheEntry項占用4個字0x00007fffe10226cf: shl $0x2,%edx
與invokevirtual的實現(xiàn)類似,這里仍然在方法沒有解釋時調(diào)用InterpreterRuntime::resolve_invoke()函數(shù)進行方法解析,后面我們也詳細介紹一下InterpreterRuntime::resolve_invoke()函數(shù)的實現(xiàn)。switch (info.call_kind()) { case CallInfo::direct_call: // 直接調(diào)用 cache_entry(thread)->set_direct_call( bytecode, info.resolved_method()); break; case CallInfo::vtable_call: // vtable分派 cache_entry(thread)->set_vtable_call( bytecode, info.resolved_method(), info.vtable_index()); break; case CallInfo::itable_call: // itable分派 cache_entry(thread)->set_itable_call( bytecode, info.resolved_method(), info.itable_index()); break; default: ShouldNotReachHere();}
之前已經(jīng)介紹過vtable分派,現(xiàn)在看一下itable分派。void ConstantPoolCacheEntry::set_itable_call( Bytecodes::Code invoke_code, methodHandle method, int index) { InstanceKlass* interf = method->method_holder(); // interf一定是接口,method一定是非final方法 set_f1(interf); // 對于itable,則_f1為InstanceKlass set_f2(index); set_method_flags(as_TosState(method->result_type()), 0, // no option bits method()->size_of_parameters()); set_bytecode_1(Bytecodes::_invokeinterface);}
ConstantPoolCacheEntry中存儲的信息為:// **** resolved ****// resolved的定義點,到這里說明invokeinterface字節(jié)碼已經(jīng)連接 // 執(zhí)行完如上匯編后寄存器的值如下:// %edx:ConstantPoolCacheEntry index// %rcx:ConstantPoolCache // 獲取到ConstantPoolCacheEntry::_f1// 在計算時,因為ConstantPoolCacheEntry在ConstantPoolCache// 之后保存,所以ConstantPoolCache為0x10,而// _f1還要偏移0x8,這樣總偏移就是0x180x00007fffe10226d2: mov 0x18(%rcx,%rdx,8),%rax // 獲取ConstantPoolCacheEntry::_f2屬性0x00007fffe10226d7: mov 0x20(%rcx,%rdx,8),%rbx// 獲取ConstantPoolCacheEntry::_flags屬性0x00007fffe10226dc: mov 0x28(%rcx,%rdx,8),%edx // 執(zhí)行如上匯編后寄存器的值如下:// %rax:ConstantPoolCacheEntry::_f1// %rbx:ConstantPoolCacheEntry::_f2// %edx:ConstantPoolCacheEntry::_flags // 將flags移動到ecx中0x00007fffe10226e0: mov %edx,%ecx// 從ConstantPoolCacheEntry::_flags中獲取參數(shù)大小0x00007fffe10226e2: and $0xff,%ecx // 讓%rcx指向recv 0x00007fffe10226e8: mov -0x8(%rsp,%rcx,8),%rcx // 暫時用%r13d保存ConstantPoolCacheEntry::_flags屬性0x00007fffe10226ed: mov %edx,%r13d // 從_flags的高4位保存的TosState中獲取方法返回類型 0x00007fffe10226f0: shr $0x1c,%edx// 將TemplateInterpreter::invoke_return_entry地址存儲到%r100x00007fffe10226f3: movabs $0x7ffff73b63e0,%r10// %rdx保存的是方法返回類型,計算返回地址// 因為TemplateInterpreter::invoke_return_entry是數(shù)組,// 所以要找到對應return type的入口地址0x00007fffe10226fd: mov (%r10,%rdx,8),%rdx// 獲取結(jié)果處理函數(shù)TemplateInterpreter::invoke_return_entry的地址并壓入棧中0x00007fffe1022701: push %rdx // 恢復ConstantPoolCacheEntry::_flags中%edx0x00007fffe1022702: mov %r13d,%edx // 還原bcp 0x00007fffe1022705: mov -0x38(%rbp),%r13
在TemplateTable::invokeinterface()函數(shù)中首先會調(diào)用prepare_invoke()函數(shù),上面的匯編就是由這個函數(shù)生成的。調(diào)用完后各個寄存器的值如下:rax: interface klass (from f1)rbx: itable index (from f2)rcx: receiverrdx: flags
然后接著執(zhí)行TemplateTable::invokeinterface()函數(shù)生成的匯編片段,如下:// 將ConstantPoolCacheEntry::_flags的值存儲到%r14d中0x00007fffe1022709: mov %edx,%r14d// 檢測一下_flags中是否含有is_forced_virtual_shift標識,如果有,// 表示調(diào)用的是Object類中的方法,需要通過vtable進行動態(tài)分派0x00007fffe102270c: and $0x800000,%r14d0x00007fffe1022713: je 0x00007fffe1022812 // 跳轉(zhuǎn)到----notMethod---- // ConstantPoolCacheEntry::_flags存儲到%eax0x00007fffe1022719: mov %edx,%eax// 測試調(diào)用的方法是否為final0x00007fffe102271b: and $0x100000,%eax0x00007fffe1022721: je 0x00007fffe1022755 // 如果為非final方法,則跳轉(zhuǎn)到----notFinal---- // 下面匯編代碼是對final方法的處理 // 對于final方法來說,rbx中存儲的是Method*,也就是ConstantPoolCacheEntry::_f2指向Method*// 跳轉(zhuǎn)到Method::from_interpreted處執(zhí)行即可0x00007fffe1022727: cmp (%rcx),%rax// ... 省略統(tǒng)計相關(guān)的代碼// 設(shè)置調(diào)用者棧頂并存儲0x00007fffe102274e: mov %r13,-0x10(%rbp)// 跳轉(zhuǎn)到Method::_from_interpreted_entry0x00007fffe1022752: jmpq *0x58(%rbx) // 調(diào)用final方法 // **** notFinal **** // 調(diào)用load_klass()函數(shù)生成如下2句匯編// 查看recv這個oop對應的Klass,存儲到%eax中0x00007fffe1022755: mov 0x8(%rcx),%eax // 調(diào)用decode_klass_not_null()函數(shù)生成的匯編 0x00007fffe1022758: shl $0x3,%rax // 省略統(tǒng)計相關(guān)的代碼 // 調(diào)用lookup_virtual_method()函數(shù)生成如下這一句匯編0x00007fffe10227fe: mov 0x1b8(%rax,%rbx,8),%rbx // 設(shè)置調(diào)用者棧頂并存儲0x00007fffe1022806: lea 0x8(%rsp),%r130x00007fffe102280b: mov %r13,-0x10(%rbp) // 跳轉(zhuǎn)到Method::_from_interpreted_entry0x00007fffe102280f: jmpq *0x58(%rbx)
如上匯編包含了對final和非final方法的分派邏輯。對于final方法來說,由于ConstantPoolCacheEntry::_f2中存儲的就是指向被調(diào)用的Method實例,所以非常簡單;對于非final方法來說,需要通過vtable實現(xiàn)動態(tài)分派。分派的關(guān)鍵一個匯編語句如下:mov 0x1b8(%rax,%rbx,8),%rbx
需要提示的是,只有少量的方法可能才會走這個邏輯進行vtable的動態(tài)分派,如調(diào)用Object類中的方法。// **** notMethod **** // 讓%r14指向本地變量表0x00007fffe1022812: mov -0x30(%rbp),%r14 // %rcx中存儲的是receiver,%edx中保存的是Klass0x00007fffe1022816: mov 0x8(%rcx),%edx // LogKlassAlignmentInBytes=0x03,進行對齊處理0x00007fffe1022819: shl $0x3,%rdx // 如下代碼是調(diào)用如下函數(shù)生成的:__ lookup_interface_method(rdx, // inputs: rec. classrax, // inputs: interfacerbx, // inputs: itable indexrbx, // outputs: methodr13, // outputs: scan temp. regno_such_interface); // 獲取vtable的起始地址 // %rdx中存儲的是recv.Klass,獲取Klass中// vtable_length屬性的值0x00007fffe10228c1: mov 0x118(%rdx),%r13d // %rdx:recv.Klass,%r13為vtable_length,// 最后r13指向第一個itableOffsetEntry// 加一個常量0x1b8是因為vtable之前是InstanceKlass0x00007fffe10228c8: lea 0x1b8(%rdx,%r13,8),%r13 0x00007fffe10228d0: lea (%rdx,%rbx,8),%rdx // 獲取itableOffsetEntry::_interface并與%rax比較,%rax中存儲的是要查找的接口0x00007fffe10228d4: mov 0x0(%r13),%rbx0x00007fffe10228d8: cmp %rbx,%rax// 如果相等,則直接跳轉(zhuǎn)到---- found_method ----0x00007fffe10228db: je 0x00007fffe10228f3 // **** search ****// 檢測%rbx中的值是否為NULL,如果為NULL,// 那就說明receiver沒有實現(xiàn)要查詢的接口0x00007fffe10228dd: test %rbx,%rbx// 跳轉(zhuǎn)到---- L_no_such_interface ----0x00007fffe10228e0: je 0x00007fffe1022a8c0x00007fffe10228e6: add $0x10,%r13 0x00007fffe10228ea: mov 0x0(%r13),%rbx0x00007fffe10228ee: cmp %rbx,%rax// 如果還是沒有在itableOffsetEntry中找到接口類,// 則跳轉(zhuǎn)到search繼續(xù)進行查找0x00007fffe10228f1: jne 0x00007fffe10228dd // 跳轉(zhuǎn)到---- search ---- // **** found_method **** // 已經(jīng)找到匹配接口的itableOffsetEntry,獲取// itableOffsetEntry的offset屬性并存儲到%r13d中0x00007fffe10228f3: mov 0x8(%r13),%r13d// 通過recv_klass進行偏移后找到此接口下聲明// 的一系列方法的開始位置0x00007fffe10228f7: mov (%rdx,%r13,1),%rbx
我們需要重點關(guān)注itable的分派邏輯,首先生成了如下匯編:mov 0x118(%rdx),%r13d
%rdx中存儲的是recv.Klass,獲取Klass中vtable_length屬性的值,有了這個值,我們就可以計算出vtable的大小,從而計算出itable的開始地址。lea 0x1b8(%rdx,%r13,8),%r13
其中的0x1b8表示的是recv.Klass首地址到vtable的距離,這樣最終的%r13指向的是itable的首地址。如下圖所示。lea (%rdx,%rbx,8),%rdx ...mov (%rdx,%r13,1),%rbx
當執(zhí)行到如上的第2個匯編時,%r13存儲的是相對于Klass實例的偏移,而%rdx在執(zhí)行第1個匯編時存儲的是Klass首地址,然后根據(jù)itable索引加上了相對于第1個itableMethodEntry的偏移,這樣就找到了對應的itableMethodEntry?! ?br>0x00007fffe10228fb: test %rbx,%rbx// 如果本來應該存儲Method*的%rbx是空,則表示沒有找到// 這個方法,跳轉(zhuǎn)到---- no_such_method ----0x00007fffe10228fe: je 0x00007fffe1022987 // 保存調(diào)用者的棧頂指針0x00007fffe1022904: lea 0x8(%rsp),%r13 0x00007fffe1022909: mov %r13,-0x10(%rbp)// 跳轉(zhuǎn)到Method::from_interpreted指向的例程并執(zhí)行0x00007fffe102290d: jmpq *0x58(%rbx) // 省略should_not_reach_here()函數(shù)生成的匯編 // **** no_such_method ****// 當沒有找到方法時,會跳轉(zhuǎn)到這里執(zhí)行 // 彈出調(diào)用prepare_invoke()函數(shù)壓入的返回地址0x00007fffe1022987: pop %rbx// 恢復讓%r13指向bcp0x00007fffe1022988: mov -0x38(%rbp),%r13// 恢復讓%r14指向本地變量表0x00007fffe102298c: mov -0x30(%rbp),%r14 // ... 省略通過call_VM()函數(shù)生成的匯編來調(diào)用InterpreterRuntime::throw_abstractMethodError()函數(shù)// ... 省略調(diào)用should_not_reach_here()函數(shù)生成的匯編代碼 // **** no_such_interface **** // 當沒有找到匹配的接口時執(zhí)行的匯編代碼0x00007fffe1022a8c: pop %rbx0x00007fffe1022a8d: mov -0x38(%rbp),%r130x00007fffe1022a91: mov -0x30(%rbp),%r14 // ... 省略通過call_VM()函數(shù)生成的匯編代碼來調(diào)用InterpreterRuntime::throw_IncompatibleClassChangeError()函數(shù)// ... 省略調(diào)用should_not_reach_here()函數(shù)生成的匯編代碼
對于一些異常的處理這里就不過多介紹了,有興趣的可以看一下相關(guān)匯編代碼的實現(xiàn)?!?br>由于字數(shù)限制,《虛擬機解釋執(zhí)行Java方法(下)》將在下篇中釋出
關(guān)鍵詞:執(zhí)行,解釋,方法,虛擬
微信公眾號
版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。