本文由HeapDump效能社群首席講師鳩摩(馬智)授權整理髮布
第34篇-解析invokeinterface位元組碼指令
與invokevirtual指令類似,當沒有對目標方法進行解析時,需要呼叫LinkResolver::resolve_invoke()函式進行解析,這個函式會呼叫其它一些函式完成方法的解析,如下圖所示。
上圖中粉色的部分與解析invokevirtual位元組碼指令有所區別,resolve_pool()函式及其呼叫的相關函式在介紹invokevirtual位元組碼指令時詳細介紹過,這裡不再介紹。
呼叫LinkResolver::resolve_invokeinterface()函式對位元組碼指令進行解析。函式的實現如下:
void LinkResolver::resolve_invokeinterface(
CallInfo& result,
Handle recv,
constantPoolHandle pool,
int index, // 指的是常量池快取項的索引
TRAPS
) {
KlassHandle resolved_klass;
Symbol* method_name = NULL;
Symbol* method_signature = NULL;
KlassHandle current_klass;
// 解析常量池時,傳入的引數pool(根據當前棧中要執行的方法找到對應的常量池)和
// index(常量池快取項的快取,還需要對映為原常量池索引)是有值的,根據這兩個值能夠
// 解析出resolved_klass和要查詢的方法名稱method_name和方法簽名method_signature
resolve_pool(resolved_klass, method_name, method_signature, current_klass, pool, index, CHECK);
KlassHandle recvrKlass (THREAD, recv.is_null() ? (Klass*)NULL : recv->klass());
resolve_interface_call(result, recv, recvrKlass, resolved_klass, method_name, method_signature, current_klass, true, true, CHECK);
}
我們接著看resolve\_interface\_call()函式的實現,如下:
void LinkResolver::resolve_interface_call(
CallInfo& result,
Handle recv,
KlassHandle recv_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_interface_method(resolved_method, resolved_klass, method_name, method_signature, current_klass, check_access, CHECK);
runtime_resolve_interface_method(result, resolved_method, resolved_klass, recv, recv_klass, check_null_and_abstract, CHECK);
}
呼叫2個函式對方法進行解析。首先看linktime\_resolve\_interface_method()函式的實現。
呼叫linktime\_resolve\_interface\_method()函式會呼叫LinkResolver::resolve\_interface_method()函式,此函式的實現如下:
void LinkResolver::resolve_interface_method(
methodHandle& resolved_method,
KlassHandle resolved_klass,
Symbol* method_name,
Symbol* method_signature,
KlassHandle current_klass,
bool check_access,
bool nostatics,
TRAPS
) {
// 從介面和父類java.lang.Object中查詢方法,包括靜態方法
lookup_method_in_klasses(resolved_method, resolved_klass, method_name, method_signature, false, true, CHECK);
if (resolved_method.is_null()) {
// 從實現的所有介面中查詢方法
lookup_method_in_interfaces(resolved_method, resolved_klass, method_name, method_signature, CHECK);
if (resolved_method.is_null()) {
// no method found
// ...
}
}
// ...
}
首先呼叫LinkResolver::lookup\_method\_in_klasses()函式進行方法查詢,在之前介紹過invokevirtual位元組碼指令時介紹過這個函式,不過只介紹了與invokevirtual指令相關的處理邏輯,這裡需要繼續檢視invokeinterface的相關處理邏輯,實現如下:
void LinkResolver::lookup_method_in_klasses(
methodHandle& result,
KlassHandle klass,
Symbol* name,
Symbol* signature,
bool checkpolymorphism,
// 對於invokevirtual來說,值為false,對於invokeinterface來說,值為true
bool in_imethod_resolve,
TRAPS
) {
Method* result_oop = klass->uncached_lookup_method(name, signature);
// 在介面中定義方法的解析過程中,忽略Object類中的靜態和非public方法,如
// clone、finalize、registerNatives
if (
in_imethod_resolve &&
result_oop != NULL &&
klass->is_interface() &&
(result_oop->is_static() || !result_oop->is_public()) &&
result_oop->method_holder() == SystemDictionary::Object_klass() // 方法定義在Object類中
) {
result_oop = NULL;
}
if (result_oop == NULL) {
Array<Method*>* default_methods = InstanceKlass::cast(klass())->default_methods();
if (default_methods != NULL) {
result_oop = InstanceKlass::find_method(default_methods, name, signature);
}
}
// ...
result = methodHandle(THREAD, result_oop);
}
呼叫uncached\_lookup\_method()函式從當前類和父類中查詢,如果沒有找到或找到的是Object類中的不合法方法,則會呼叫find_method()函式從預設方法中查詢。在Java8的新特性中有一個新特性為介面預設方法,該新特性允許我們在介面中新增一個非抽象的方法實現,而這樣做的方法只需要使用關鍵字default修飾該預設實現方法即可。
uncached\_lookup\_method()函式的實現如下:
Method* InstanceKlass::uncached_lookup_method(Symbol* name, Symbol* signature) const {
Klass* klass = const_cast<InstanceKlass*>(this);
bool dont_ignore_overpasses = true;
while (klass != NULL) {
Method* method = InstanceKlass::cast(klass)->find_method(name, signature);
if ((method != NULL) && (dont_ignore_overpasses || !method->is_overpass())) {
return method;
}
klass = InstanceKlass::cast(klass)->super();
dont_ignore_overpasses = false; // 不要搜尋父類中的overpass方法
}
return NULL;
}
從當前類和父類中查詢方法。當從類和父類中查詢方法時,呼叫find\_method()函式,最終呼叫另外一個過載函式find\_method()從InstanceKlass::\_methods屬性中儲存的方法中進行查詢;當從預設方法中查詢方法時,呼叫find\_method()函式從InstanceKlass::\_default\_methods屬性中儲存的方法中查詢。過載的find_method()函式的實現如下:
Method* InstanceKlass::find_method(Array<Method*>* methods, Symbol* name, Symbol* signature) {
int hit = find_method_index(methods, name, signature);
return hit >= 0 ? methods->at(hit): NULL;
}
其實呼叫find\_method\_index()函式就是根據二分查詢來找名稱為name,簽名為signature的方法,因為InstanceKlass::\_methods和InstanceKlass::\_default_methods屬性中的方法已經進行了排序,關於這些函式中儲存的方法及如何進行排序在《深入剖析Java虛擬機器:原始碼剖析與例項詳解(基礎卷)》一書中詳細介紹過,這裡不再介紹。
呼叫的LinkResolver::runtime\_resolve\_interface_method()函式的實現如下:
void LinkResolver::runtime_resolve_interface_method(
CallInfo& result,
methodHandle resolved_method,
KlassHandle resolved_klass,
Handle recv,
KlassHandle recv_klass,
bool check_null_and_abstract, // 對於invokeinterface來說,值為false
TRAPS
) {
// ...
methodHandle sel_method;
lookup_instance_method_in_klasses(
sel_method,
recv_klass,
resolved_method->name(),
resolved_method->signature(),
CHECK);
if (sel_method.is_null() && !check_null_and_abstract) {
sel_method = resolved_method;
}
// ...
// 如果查詢介面的實現時找到的是Object類中的方法,那麼要通過vtable進行分派,所以我們需要
// 更新的是vtable相關的資訊
if (!resolved_method->has_itable_index()) {
int vtable_index = resolved_method->vtable_index();
assert(vtable_index == sel_method->vtable_index(), "sanity check");
result.set_virtual(resolved_klass, recv_klass, resolved_method, sel_method, vtable_index, CHECK);
} else {
int itable_index = resolved_method()->itable_index();
result.set_interface(resolved_klass, recv_klass, resolved_method, sel_method, itable_index, CHECK);
}
}
當沒有itable索引時,通過vtable進行動態分派;否則通過itable進行動態分派。
呼叫的lookup\_instance\_method\_in\_klasses()函式的實現如下:
void LinkResolver::lookup_instance_method_in_klasses(
methodHandle& result,
KlassHandle klass,
Symbol* name,
Symbol* signature,
TRAPS
) {
Method* result_oop = klass->uncached_lookup_method(name, signature);
result = methodHandle(THREAD, result_oop);
// 迴圈查詢方法的實現,不會查詢靜態方法
while (!result.is_null() && result->is_static() && result->method_holder()->super() != NULL) {
KlassHandle super_klass = KlassHandle(THREAD, result->method_holder()->super());
result = methodHandle(THREAD, super_klass->uncached_lookup_method(name, signature));
}
// 當從擁有Itable的類或父類中找到介面中方法的實現時,result不為NULL,
// 否則為NULL,這時候就要查詢預設的方法實現了,這也算是一種實現
if (result.is_null()) {
Array<Method*>* default_methods = InstanceKlass::cast(klass())->default_methods();
if (default_methods != NULL) {
result = methodHandle(InstanceKlass::find_method(default_methods, name, signature));
}
}
}
如上在查詢預設方法實現時會呼叫find_method()函式,此函式在之前介紹invokevirtual位元組碼指令的解析過程時詳細介紹過,這裡不再介紹。
在LinkResolver::runtime\_resolve\_interface\_method()函式的最後有可能呼叫CallInfo::set\_interface()或CallInfo::set\_virtual()函式,呼叫這兩個函式就是將查詢到的資訊儲存到CallInfo例項中。最終會在InterpreterRuntime::resolve\_invoke()函式中根據CallInfo例項中儲存的資訊更新ConstantPoolCacheEntry相關的資訊,如下:
switch (info.call_kind()) {
// ...
case CallInfo::itable_call:
cache_entry(thread)->set_itable_call(
bytecode,
info.resolved_method(),
info.itable_index());
break;
default: ShouldNotReachHere();
}
當CallInfo中儲存的是itable的分派資訊時,呼叫set\_itable\_call()函式,這個函式的實現如下:
void ConstantPoolCacheEntry::set_itable_call(
Bytecodes::Code invoke_code,
methodHandle method,
int index
) {
assert(invoke_code == Bytecodes::_invokeinterface, "");
InstanceKlass* interf = method->method_holder();
// interf一定是介面,而method一定是非final方法
set_f1(interf); // 對於itable,_f1儲存的是表示介面的InstanceKlass
set_f2(index); // 對於itable,_f2儲存的是itable索引
set_method_flags(as_TosState(method->result_type()),
0, // no option bits
method()->size_of_parameters());
set_bytecode_1(Bytecodes::_invokeinterface);
}
使用CallInfo例項中的資訊更新ConstantPoolCacheEntry中的資訊即可。
第35篇-方法呼叫指令之invokespecial與invokestatic字
這一篇將詳細介紹invokespecial和invokestatic位元組碼指令的彙編實現邏輯
1、invokespecial指令
invokespecial指令的模板定義如下:
def(Bytecodes::_invokespecial , ubcp|disp|clvm|____, vtos, vtos, invokespecial , f1_byte );
生成函式為invokespecial(),生成的彙編程式碼如下:
0x00007fffe1022250: mov %r13,-0x38(%rbp)
0x00007fffe1022254: movzwl 0x1(%r13),%edx
0x00007fffe1022259: mov -0x28(%rbp),%rcx
0x00007fffe102225d: shl $0x2,%edx
0x00007fffe1022260: mov 0x10(%rcx,%rdx,8),%ebx
// 獲取ConstantPoolCacheEntry中indices[b2,b1,constant pool index]中的b1
0x00007fffe1022264: shr $0x10,%ebx
0x00007fffe1022267: and $0xff,%ebx
// 檢查invokespecial=183的bytecode是否已經連線,如果已經連線就進行跳轉
0x00007fffe102226d: cmp $0xb7,%ebx
0x00007fffe1022273: je 0x00007fffe1022312
// ... 省略呼叫InterpreterRuntime::resolve_invoke()函式
// 對invokespecial=183的bytecode進行連線,
// 因為位元組碼指令還沒有連線
// 將invokespecial x中的x載入到%edx中
0x00007fffe1022306: movzwl 0x1(%r13),%edx
// 將ConstantPoolCache的首地址儲存到%rcx中
0x00007fffe102230b: mov -0x28(%rbp),%rcx
// %edx中儲存的是ConstantPoolCacheEntry項的索引,轉換為字偏移
0x00007fffe102230f: shl $0x2,%edx
// 獲取ConstantPoolCache::_f1屬性的值
0x00007fffe1022312: mov 0x18(%rcx,%rdx,8),%rbx
// 獲取ConstantPoolCache::_flags屬性的值
0x00007fffe1022317: mov 0x28(%rcx,%rdx,8),%edx
// 將flags移動到ecx中
0x00007fffe102231b: mov %edx,%ecx
// 從flags中取出引數大小
0x00007fffe102231d: and $0xff,%ecx
// 獲取到recv,%rcx中儲存的是引數大小,最終計算為 %rsp+%rcx*8-0x8,
// flags中的引數大小可能對例項方法來說,已經包括了recv的大小
// 如呼叫例項方法的第一個引數是this(recv)
0x00007fffe1022323: mov -0x8(%rsp,%rcx,8),%rcx
// 從flags中獲取return type,也就是從_flags的高4位儲存的TosState
0x00007fffe1022328: shr $0x1c,%edx
// 將TemplateInterpreter::invoke_return_entry地址儲存到%r10
0x00007fffe102232b: movabs $0x7ffff73b6380,%r10
// 找到對應return type的invoke_return_entry的地址
0x00007fffe1022335: mov (%r10,%rdx,8),%rdx
// 通過invokespecial指令呼叫函式後的返回地址
0x00007fffe1022339: push %rdx
// 空值檢查
0x00007fffe102233a: cmp (%rcx),%rax
// ...
// 設定呼叫者棧頂
0x00007fffe102235c: lea 0x8(%rsp),%r13
// 向棧中last_sp的位置儲存呼叫者棧頂
0x00007fffe1022361: mov %r13,-0x10(%rbp)
// 跳轉到Method::_from_interpretered_entry入口去執行
0x00007fffe1022365: jmpq *0x58(%rbx)
invokespecial指令在呼叫private和構造方法時,不需要動態分發。在這個位元組碼指令解析完成後,ConstantPoolCacheEntry中的\_f1指向目標方法的Method例項,\_f2沒有使用,所以如上彙編的邏輯非常簡單,這裡不再過多介紹。
2、invokestatic指令
invokestatic指令的模板定義如下:
def(Bytecodes::_invokestatic , ubcp|disp|clvm|____, vtos, vtos, invokestatic , f1_byte);
生成函式為invokestatic(),生成的彙編程式碼如下:
0x00007fffe101c030: mov %r13,-0x38(%rbp)
0x00007fffe101c034: movzwl 0x1(%r13),%edx
0x00007fffe101c039: mov -0x28(%rbp),%rcx
0x00007fffe101c03d: shl $0x2,%edx
0x00007fffe101c040: mov 0x10(%rcx,%rdx,8),%ebx
0x00007fffe101c044: shr $0x10,%ebx
0x00007fffe101c047: and $0xff,%ebx
0x00007fffe101c04d: cmp $0xb8,%ebx
// 檢查invokestatic=184的bytecode是否已經連線,如果已經連線就進行跳轉
0x00007fffe101c053: je 0x00007fffe101c0f2
// 呼叫InterpreterRuntime::resolve_invoke()函式對invokestatic=184的
// 的bytecode進行連線,因為位元組碼指令還沒有連線
// ... 省略瞭解析invokestatic的彙編程式碼
// 將invokestatic x中的x載入到%edx中
0x00007fffe101c0e6: movzwl 0x1(%r13),%edx
// 將ConstantPoolCache的首地址儲存到%rcx中
0x00007fffe101c0eb: mov -0x28(%rbp),%rcx
// %edx中儲存的是ConstantPoolCacheEntry項的索引,轉換為字偏移
0x00007fffe101c0ef: shl $0x2,%edx
// 獲取ConstantPoolCache::_f1屬性的值
0x00007fffe101c0f2: mov 0x18(%rcx,%rdx,8),%rbx
// 獲取ConstantPoolCache::_flags屬性的值
0x00007fffe101c0f7: mov 0x28(%rcx,%rdx,8),%edx
// 從flags中獲取return type,也就是從_flags的高4位儲存的TosState
0x00007fffe101c0fb: shr $0x1c,%edx
// 將TemplateInterpreter::invoke_return_entry地址儲存到%r10
0x00007fffe101c0fe: movabs $0x7ffff73b5d00,%r10
// 找到對應return type的invoke_return_entry的地址
0x00007fffe101c108: mov (%r10,%rdx,8),%rdx
// 通過invokespecial指令呼叫函式後的返回地址
0x00007fffe101c10c: push %rdx
// 設定呼叫者棧頂
0x00007fffe101c10d: lea 0x8(%rsp),%r13
// 向棧中last_sp的位置儲存呼叫者棧頂
0x00007fffe101c112: mov %r13,-0x10(%rbp)
// 跳轉到Method::_from_interpretered_entry入口去執行
0x00007fffe101c116: jmpq *0x58(%rbx)
invokespecial指令在呼叫靜態方法時,不需要動態分發。在這個位元組碼指令解析完成後,ConstantPoolCacheEntry中的\_f1指向目標方法的Method例項,\_f2沒有使用,所以如上彙編的邏輯非常簡單,這裡不再過多介紹。
關於invokestatic與invokespecial的解析過程這裡就不再過多介紹了,有興趣的可從LinkResolver::resolve_invoke()函式檢視具體的解析過程。
第36篇-方法返回指令之return
方法返回的位元組碼相關指令如下表所示。
模板定義如下:
def(Bytecodes::_ireturn , ____|disp|clvm|____, itos, itos, _return , itos );
def(Bytecodes::_lreturn , ____|disp|clvm|____, ltos, ltos, _return , ltos );
def(Bytecodes::_freturn , ____|disp|clvm|____, ftos, ftos, _return , ftos );
def(Bytecodes::_dreturn , ____|disp|clvm|____, dtos, dtos, _return , dtos );
def(Bytecodes::_areturn , ____|disp|clvm|____, atos, atos, _return , atos );
def(Bytecodes::_return , ____|disp|clvm|____, vtos, vtos, _return , vtos );
def(Bytecodes::_return_register_finalizer , ____|disp|clvm|____, vtos, vtos, _return , vtos );
生成函式都為TemplateTable::\_return()。但是如果是Object物件的構造方法中的return指令,那麼這個指令還可能會被重寫為\_return\_register\_finalizer指令。
生成的return位元組碼指令對應的彙編程式碼如下:
第1部分
// 將JavaThread::do_not_unlock_if_synchronized屬性儲存到%dl中
0x00007fffe101b770: mov 0x2ad(%r15),%dl
// 重置JavaThread::do_not_unlock_if_synchronized屬性值為false
0x00007fffe101b777: movb $0x0,0x2ad(%r15)
// 將Method*載入到%rbx中
0x00007fffe101b77f: mov -0x18(%rbp),%rbx
// 將Method::_access_flags載入到%ecx中
0x00007fffe101b783: mov 0x28(%rbx),%ecx
// 檢查Method::flags是否包含JVM_ACC_SYNCHRONIZED
0x00007fffe101b786: test $0x20,%ecx
// 如果方法不是同步方法,跳轉到----unlocked----
0x00007fffe101b78c: je 0x00007fffe101b970
// 如果在%dl暫存器中儲存的_do_not_unlock_if_synchronized的值不為0,
// 則跳轉到no_unlock,表示不要釋放和鎖相關的資源
0x00007fffe101b792: test $0xff,%dl
0x00007fffe101b795: jne
0x00007fffe101ba90 // 跳轉到----no_unlock----處
在JavaThread類中定義了一個屬性\_do\_not\_unlock\_if\_synchronized,這個值表示在丟擲異常的情況下不要釋放receiver(在非靜態方法呼叫的情況下,我們總是會將方法解析到某個物件上,這個物件就是這裡的receiver,也可稱為接收者),此值僅在解釋執行的情況下才會起作用。初始的時候會初始化為false。在如上彙編中可以看到,當\_do\_not\_unlock\_if\_synchronized的值為true時,表示不需要釋放receiver,所以雖然當前是同步方法,但是卻直接呼叫到了no_unlock處。
第2部分
如果執行如下彙編程式碼,則表示%dl暫存器中儲存的\_do\_not\_unlock\_if_synchronized的值為0,需要執行釋放鎖的操作。
// 將之前位元組碼指令執行的結果儲存到表示式棧頂,
// 由於return不需要返回執行結果,所以不需要設定返回值等資訊,
// 最終在這裡沒有生成任何push指令
// 將BasicObjectLock儲存到%rsi中,由於%rsi在呼叫C++函式時可做為
// 第2個引數傳遞,所以如果要呼叫unlock_object就可以傳遞此值
0x00007fffe101b79b: lea -0x50(%rbp),%rsi
// 獲取BasicObjectLock::obj屬性地址儲存到%rax中
0x00007fffe101b79f: mov 0x8(%rsi),%rax
// 如果不為0,則跳轉到unlock處,因為不為0,表示
// 這個obj有指向的鎖物件,需要進行釋放鎖的操作
0x00007fffe101b7a3: test %rax,%rax
0x00007fffe101b7a6: jne 0x00007fffe101b8a8 // 跳轉到----unlock----處
// 如果是其它的return指令,則由於之前通過push指令將結果儲存在
// 表示式棧上,所以現在可通過pop將表示式棧上的結果彈出到對應暫存器中
第1個指令的-0x50(%rbp)指向了第1個BasicObjectLock物件,其中的sizeof(BasicObjectLock)的值為16,也就是16個位元組。在之前我們介紹棧幀的時候介紹過Java解釋棧的結構,如下:
假設當前的棧幀中有2個鎖物件,則會在棧幀中儲存2個BasicObjectLock物件,BasicObjectLock中有2個屬性,\_lock和\_obj,分別佔用8位元組。佈局如下圖所示。
由於return位元組碼指令負責要釋放的是加synchronized關鍵字的、解釋執行的Java方法,所以為synchronized關鍵字建立的第1個鎖物件儲存在離當前棧幀最靠近棧底的地方,也就是上圖中灰色部分,而其它鎖物件我們暫時不用管。灰色部分表示的BasicObjectLock的地址通過-0x50(%rbp)就能獲取到,然後對其中的\_lock和\_obj屬性進行操作。
由於現在還沒有介紹鎖相關的知識,所以這裡不做過多介紹,在後面介紹完鎖相關知識後還會詳細介紹。
第3部分
在變數throw\_monitor\_exception為true的情況下,通過呼叫call\_VM()函式生成丟擲鎖狀態異常的彙編程式碼,這些彙編程式碼主要是為了執行C++函式InterpreterRuntime::throw\_illegal\_monitor\_state\_exception()。完成執行後還會執行由should\_not\_reach\_here()函式生成的彙編程式碼。
在變數throw\_monitor\_exception為false並且install\_monitor\_exception為true的情況下,通過呼叫call\_VM()函式生成彙編程式碼來執行C++函式InterpreterRuntime::new\_illegal\_monitor\_state_exception()。最後跳轉到unlocked處執行。
第4部分
在InterpreterMacroAssembler::remove\_activation()函式中,bind完unlock後就會呼叫InterpreterMacroAssembler::unlock\_object()函式生成如下的彙編程式碼。InterpreterMacroAssembler::unlock_object()函式的作用如下:
Unlocks an object. Used in monitorexit bytecode and remove_activation. Throws an IllegalMonitorException if object is not locked by current thread.
生成的彙編程式碼如下:
// **** unlock ****
// ============呼叫InterpreterMacroAssembler::unlock_object()函式生成如下的彙編程式碼==================
// 將%r13儲存到棧中,防止異常破壞了%r13暫存器中的值
0x00007fffe101b8a8: mov %r13,-0x38(%rbp)
// 將BasicObjectLock::_lock的地址儲存到%rax暫存器中
0x00007fffe101b8ac: lea (%rsi),%rax
// 將BasicObjectLock::_obj儲存到%rcx暫存器中
0x00007fffe101b8af: mov 0x8(%rsi),%rcx
// 將BasicObjectLock::_obj的值設定為NULL,表示釋放鎖操作
0x00007fffe101b8b3: movq $0x0,0x8(%rsi)
// ----------當UseBiasedLocking的值為true時,呼叫MacroAssembler::biased_locking_exit()生成如下的彙編程式碼------------
// 從BasicObjectLock::_obj物件中取出mark屬性值並相與
0x00007fffe101b8bb: mov (%rcx),%rdx
0x00007fffe101b8be: and $0x7,%rdx
// 如果BasicObjectLock::_obj指向的oop的mark屬性後3位是偏向鎖的狀態,則跳轉到---- done ----
0x00007fffe101b8c2: cmp $0x5,%rdx
0x00007fffe101b8c6: je 0x00007fffe101b96c
// ------------------------結束呼叫MacroAssembler::biased_locking_exit()生成的彙編程式碼---------------------
// 將BasicObjectLock::_lock這個oop物件的_displaced_header屬性值取出
0x00007fffe101b8cc: mov (%rax),%rdx
// 判斷一下是否為鎖的重入,如果是鎖的重入,則跳轉到---- done ----
0x00007fffe101b8cf: test %rdx,%rdx
0x00007fffe101b8d2: je 0x00007fffe101b96c
// 讓BasicObjectLock::_obj的那個oop的mark恢復為
// BasicObjectLock::_lock中儲存的原物件頭
0x00007fffe101b8d8: lock cmpxchg %rdx,(%rcx)
// 如果為0,則表示鎖的重入,跳轉到---- done ---- ????
0x00007fffe101b8dd: je 0x00007fffe101b96c
// 讓BasicObjectLock::_obj指向oop,這個oop的物件頭已經替換為了BasicObjectLock::_lock中儲存的物件頭
0x00007fffe101b8e3: mov %rcx,0x8(%rsi)
// -----------呼叫call_VM()函式生成彙編程式碼來執行C++函式InterpreterRuntime::monitorexit()----------------
0x00007fffe101b8e7: callq 0x00007fffe101b8f1
0x00007fffe101b8ec: jmpq 0x00007fffe101b96c
0x00007fffe101b8f1: lea 0x8(%rsp),%rax
0x00007fffe101b8f6: mov %r13,-0x38(%rbp)
0x00007fffe101b8fa: mov %r15,%rdi
0x00007fffe101b8fd: mov %rbp,0x200(%r15)
0x00007fffe101b904: mov %rax,0x1f0(%r15)
0x00007fffe101b90b: test $0xf,%esp
0x00007fffe101b911: je 0x00007fffe101b929
0x00007fffe101b917: sub $0x8,%rsp
0x00007fffe101b91b: callq 0x00007ffff66b3d22
0x00007fffe101b920: add $0x8,%rsp
0x00007fffe101b924: jmpq 0x00007fffe101b92e
0x00007fffe101b929: callq 0x00007ffff66b3d22
0x00007fffe101b92e: movabs $0x0,%r10
0x00007fffe101b938: mov %r10,0x1f0(%r15)
0x00007fffe101b93f: movabs $0x0,%r10
0x00007fffe101b949: mov %r10,0x200(%r15)
0x00007fffe101b950: cmpq $0x0,0x8(%r15)
0x00007fffe101b958: je 0x00007fffe101b963
0x00007fffe101b95e: jmpq 0x00007fffe1000420
0x00007fffe101b963: mov -0x38(%rbp),%r13
0x00007fffe101b967: mov -0x30(%rbp),%r14
0x00007fffe101b96b: retq
// ------------------------結束call_VM()函式呼叫生成的彙編程式碼--------------------------------
// **** done ****
0x00007fffe101b96c: mov -0x38(%rbp),%r13
0x00007fffe101b970: mov -0x40(%rbp),%rsi
// ==========結束呼叫InterpreterMacroAssembler::unlock_object()函式生成如下的彙編程式碼============
第5部分
// 如果是其它的return指令,則由於之前通過push指令將結果儲存在
// 表示式棧上,所以現在可通過pop將表示式棧上的結果彈出到對應暫存器中
// **** unlocked ****
// 在執行這裡的程式碼時,表示當前的棧中沒有相關的鎖,也就是
// 相關的鎖物件已經全部釋放
// **** restart ****
// 檢查一下,是否所有的鎖都已經釋放了
// %rsi指向當前棧中最靠棧頂的BasicObjectLock
0x00007fffe101b970: mov -0x40(%rbp),%rsi
// %rbx指向當前棧中最靠棧底的BasicObjectLock
0x00007fffe101b974: lea -0x40(%rbp),%rbx
// 跳轉到----entry----
0x00007fffe101b978: jmpq 0x00007fffe101ba8b
第6部分
執行如下程式碼,會通過呼叫call\_VM()函式來生成呼叫InterpreterRuntime::throw\_illegal\_monitor\_state_exception()函式的程式碼:
// **** exception ****
// Entry already locked, need to throw exception
// 當throw_monitor_exception的值為true時,執行如下2個函式生成的彙編程式碼:
// 執行call_VM()函式生成的彙編程式碼,就是呼叫C++函式InterpreterRuntime::throw_illegal_monitor_state_exception()
// 執行should_not_reach_here()函式生成的彙編程式碼
// 當throw_monitor_exception的值為false,執行如下彙編:
// 執行呼叫InterpreterMacroAssembler::unlock_object()函式生成的彙編程式碼
// install_monitor_exception的值為true時,執行call_VM()函式生成的彙編程式碼,就是呼叫C++函式InterpreterRuntime::new_illegal_monitor_state_exception()
// 無條件跳轉到----restart ----
第7部分
// **** loop ****
// 將BasicObjectLock::obj與NULL比較,如果不相等,則跳轉到----exception----
0x00007fffe101ba79: cmpq $0x0,0x8(%rsi)
0x00007fffe101ba81: jne 0x00007fffe101b97d // 則跳轉到----exception----
第8部分
// **** entry ****
// 0x10為BasicObjectLock,找到下一個BasicObjectLock
0x00007fffe101ba87: add $0x10,%rsi
// 檢查是否到達了鎖物件儲存區域的底部
0x00007fffe101ba8b: cmp %rbx,%rsi
// 如果不相等,跳轉到loop
0x00007fffe101ba8e: jne 0x00007fffe101ba79 // 跳轉到----loop----
第9部分
// **** no_unlock ****
// 省略jvmti support
// 將-0x8(%rbp)處儲存的old stack pointer(saved rsp)取出來放到%rbx中
0x00007fffe101bac7: mov -0x8(%rbp),%rbx
// 移除棧幀
// leave指令相當於:
// mov %rbp, %rsp
// pop %rbp
0x00007fffe101bacb: leaveq
// 將返回地址彈出到%r13中
0x00007fffe101bacc: pop %r13
// 設定%rsp為呼叫者的棧頂值
0x00007fffe101bace: mov %rbx,%rsp
0x00007fffe101bad1: jmpq *%r13
其中的解釋方法返回地址為return address,由於當前是C++函式呼叫Java,所以這個返回地址其實是C++函式的返回地址,我們不需要考慮。
整個的呼叫轉換如下圖所示。
其中的紅色部分表示終結這個流程。
在return位元組碼指令中會涉及到鎖釋放的流程,所以上面的流程圖看起來會複雜一些,等我們介紹完鎖相關知識後會再次介紹return指令,這裡不再過多介紹。
第37篇-恢復呼叫者棧幀例程Interpreter::\_invoke\_return_entry
我們在之前介紹過return位元組碼指令的執行邏輯,這個位元組碼指令只會執行釋放鎖和退出當前棧幀的操作,但是當控制權轉移給呼叫者時,還需要恢復呼叫者的棧幀狀態,如讓%r13指向bcp、%r14指向區域性變數表等,另外還需要彈出壓入的實參、跳轉到呼叫者的下一個位元組碼指令繼續執行,而這一切操作都是由Interpreter::\_return\_entry例程負責的。這個例程在之前介紹invokevirtual和invokeinterface等位元組碼指令時介紹過,當使用這些位元組碼指令呼叫方法時,會根據方法的返回型別壓入Interpreter::\_return\_entry一維陣列中儲存的對應例程地址,這樣return位元組碼指令執行完成後就會執行這段例程。
在invokevirtual和invokeinterface等位元組碼指令中通過呼叫如下函式獲取對應的例程入口:
address* TemplateInterpreter::invoke_return_entry_table_for(Bytecodes::Code code) {
switch (code) {
case Bytecodes::_invokestatic:
case Bytecodes::_invokespecial:
case Bytecodes::_invokevirtual:
case Bytecodes::_invokehandle:
return Interpreter::invoke_return_entry_table();
case Bytecodes::_invokeinterface:
return Interpreter::invokeinterface_return_entry_table();
default:
fatal(err_msg("invalid bytecode: %s", Bytecodes::name(code)));
return NULL;
}
}
可以看到invokeinterface位元組碼從Interpreter::\_invokeinterface\_return\_entry陣列中獲取對應的例程,而其它的從Interpreter::\_invoke\_return\_entry一維陣列中獲取。如下:
address TemplateInterpreter::_invoke_return_entry[TemplateInterpreter::number_of_return_addrs];
address TemplateInterpreter::_invokeinterface_return_entry[TemplateInterpreter::number_of_return_addrs];
當返回一維陣列後,會根據方法返回型別進一步確定例程入口地址。下面我們就看一下這些例程的生成過程。
TemplateInterpreterGenerator::generate\_all()函式中會生成Interpreter::\_return_entry入口,如下:
{
CodeletMark cm(_masm, "invoke return entry points");
const TosState states[] = {itos, itos, itos, itos, ltos, ftos, dtos, atos, vtos};
const int invoke_length = Bytecodes::length_for(Bytecodes::_invokestatic); // invoke_length=3
const int invokeinterface_length = Bytecodes::length_for(Bytecodes::_invokeinterface); // invokeinterface=5
for (int i = 0; i < Interpreter::number_of_return_addrs; i++) { // number_of_return_addrs = 9
TosState state = states[i]; // TosState是列舉型別
Interpreter::_invoke_return_entry[i] = generate_return_entry_for(state, invoke_length, sizeof(u2));
Interpreter::_invokeinterface_return_entry[i] = generate_return_entry_for(state, invokeinterface_length, sizeof(u2));
}
}
除invokedynamic位元組碼指令外,其它的方法呼叫指令在解釋執行完成後都需要呼叫由generate\_return\_entry\_for()函式生成的例程,生成例程的generate\_return\_entry\_for()函式實現如下:
address TemplateInterpreterGenerator::generate_return_entry_for(TosState state, int step, size_t index_size) {
// Restore stack bottom in case萬一 i2c adjusted stack
__ movptr(rsp, Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize)); // interpreter_frame_last_sp_offset=-2
// and NULL it as marker that esp is now tos until next java call
__ movptr(Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize), (int32_t)NULL_WORD);
__ restore_bcp();
__ restore_locals();
// ...
const Register cache = rbx;
const Register index = rcx;
__ get_cache_and_index_at_bcp(cache, index, 1, index_size);
const Register flags = cache;
__ movl(flags, Address(cache, index, Address::times_ptr, ConstantPoolCache::base_offset() + ConstantPoolCacheEntry::flags_offset()));
__ andl(flags, ConstantPoolCacheEntry::parameter_size_mask);
__ lea(rsp, Address(rsp, flags, Interpreter::stackElementScale()) ); // 棧元素標量為8
__ dispatch_next(state, step);
return entry;
}
根據state的不同(方法的返回型別的不同),會在選擇執行呼叫者方法的下一個位元組碼指令時,決定要從位元組碼指令的哪個入口處開始執行。我們看一下,當傳遞的state為itos(也就是當方法的返回型別為int時)時生成的彙編程式碼如下:
// 將-0x10(%rbp)儲存到%rsp後,置空-0x10(%rbp)
0x00007fffe1006ce0: mov -0x10(%rbp),%rsp // 更改rsp
0x00007fffe1006ce4: movq $0x0,-0x10(%rbp) // 更改棧中特定位置的值
// 恢復bcp和locals,使%r14指向本地變數表,%r13指向bcp
0x00007fffe1006cec: mov -0x38(%rbp),%r13
0x00007fffe1006cf0: mov -0x30(%rbp),%r14
// 獲取ConstantPoolCacheEntry的索引並載入到%ecx
0x00007fffe1006cf4: movzwl 0x1(%r13),%ecx
// 獲取棧中-0x28(%rbp)的ConstantPoolCache並載入到%ecx
0x00007fffe1006cf9: mov -0x28(%rbp),%rbx
// shl是邏輯左移,獲取字偏移
0x00007fffe1006cfd: shl $0x2,%ecx
// 獲取ConstantPoolCacheEntry中的_flags屬性值
0x00007fffe1006d00: mov 0x28(%rbx,%rcx,8),%ebx
// 獲取_flags中的低8位中儲存的引數大小
0x00007fffe1006d04: and $0xff,%ebx
// lea指令將地址載入到記憶體暫存器中,也就是恢復呼叫方法之前棧的樣子
0x00007fffe1006d0a: lea (%rsp,%rbx,8),%rsp
// 跳轉到下一指令執行
0x00007fffe1006d0e: movzbl 0x3(%r13),%ebx
0x00007fffe1006d13: add $0x3,%r13
0x00007fffe1006d17: movabs $0x7ffff73b7ca0,%r10
0x00007fffe1006d21: jmpq *(%r10,%rbx,8)
如上彙編程式碼的邏輯非常簡單,這裡不再過多介紹。
第38篇-解釋方法之間的呼叫小例項
這一篇我們介紹一下解釋執行的main()方法呼叫解析執行的add()方法的小例項,這個例子如下:
package com.classloading;
public class TestInvokeMethod {
public int add(int a, int b) {
return a + b;
}
public static void main(String[] args) {
TestInvokeMethod tim = new TestInvokeMethod();
tim.add(2, 3);
}
}
通過Javac編譯器編譯為位元組碼檔案,如下:
Constant pool:
#1 = Methodref #5.#16 // java/lang/Object."<init>":()V
#2 = Class #17 // com/classloading/TestInvokeMethod
#3 = Methodref #2.#16 // com/classloading/TestInvokeMethod."<init>":()V
#4 = Methodref #2.#18 // com/classloading/TestInvokeMethod.add:(II)I
#5 = Class #19 // java/lang/Object
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 add
#11 = Utf8 (II)I
#12 = Utf8 main
#13 = Utf8 ([Ljava/lang/String;)V
#14 = Utf8 SourceFile
#15 = Utf8 TestInvokeMethod.java
#16 = NameAndType #6:#7 // "<init>":()V
#17 = Utf8 com/classloading/TestInvokeMethod
#18 = NameAndType #10:#11 // add:(II)I
#19 = Utf8 java/lang/Object
{
public com.classloading.TestInvokeMethod();
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
public int add(int, int);
descriptor: (II)I
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: iadd
3: ireturn
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: new #2 // class com/classloading/TestInvokeMethod
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: aload_1
9: iconst_2
10: iconst_3
11: invokevirtual #4 // Method add:(II)I
14: pop
15: return
}
下面分幾部分介紹呼叫相關的內容。
1.C++函式呼叫main()方法
現在我們從位元組碼索引為8的aload_1開始看,此時的棧幀狀態如下:
由於aload\_1的tos\_out為atos,所以在棧頂快取的暫存器中會快取有TestInvokeMethod例項的地址,當執行iconst\_2時,會從atos進入。iconst\_2指令的彙編如下:
// aep
push %rax
jmpq // 跳轉到下面那條指令執行
// ...
mov $0x2,%eax // 指令的彙編程式碼
由於iconst\_2的tos\_out為itos,所以在進入下一個指令時,會從iconst\_3的tos\_int為itos中進入,如下:
// iep
push %rax
mov $0x3,%eax
接下來就是執行invokevirtual位元組碼指令了,此時的2已經壓入了表示式棧,而3在%eax暫存器中做為棧頂快取,但是invokevirtual的tos_in為vtos,所以從invokevirtual位元組碼指令的iep進入時會將%eax暫存器中的值也壓入表示式棧中,最終的棧狀態如下圖所示。
2.main()方法呼叫add()方法
invokevirtual位元組碼指令在執行時,假設此位元組碼指令已經解析完成,也就是對應的ConstantPoolCacheEntry中已經儲存了方法呼叫相關的資訊,則執行的相關彙編程式碼如下:
0x00007fffe1021f90: mov %r13,-0x38(%rbp) // 將bcp儲存到棧中
// invokevirtual x中取出x,也就是常量池索引儲存到%edx,
// 其實這裡已經是ConstantPoolCacheEntry的index,因為在類的連線
// 階段會對方法中特定的一些位元組碼指令進行重寫
0x00007fffe1021f94: movzwl 0x1(%r13),%edx
// 將ConstantPoolCache的首地址儲存到%rcx
0x00007fffe1021f99: 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位元組,%rcx+0x10定位
// 到第一個ConstantPoolCacheEntry的位置
// %rdx*8算出來的是相對於第一個ConstantPoolCacheEntry的位元組偏移
0x00007fffe1021fa0: mov 0x10(%rcx,%rdx,8),%ebx
// 獲取ConstantPoolCacheEntry中indices[b2,b1,constant pool index]中的b2
0x00007fffe1021fa4: shr $0x18,%ebx
// 取出indices中含有的b2,即bytecode儲存到%ebx中
0x00007fffe1021fa7: and $0xff,%ebx
// 檢視182的bytecode是否已經連線
0x00007fffe1021fad: cmp $0xb6,%ebx
// 如果連線就進行跳轉,跳轉到resolved
0x00007fffe1021fb3: je 0x00007fffe1022052
我們直接看方法解析後的邏輯實現,如下:
// **** resolved ****
// resolved的定義點,到這裡說明invokevirtual位元組碼已經連線
// 獲取ConstantPoolCacheEntry::_f2,這個欄位只對virtual有意義
// 在計算時,因為ConstantPoolCacheEntry在ConstantPoolCache之後儲存,
// 所以ConstantPoolCache為0x10,而
// _f2還要偏移0x10,這樣總偏移就是0x20
// ConstantPoolCacheEntry::_f2儲存到%rbx
0x00007fffe1022052: mov 0x20(%rcx,%rdx,8),%rbx
// ConstantPoolCacheEntry::_flags儲存到%edx
0x00007fffe1022057: mov 0x28(%rcx,%rdx,8),%edx
// 將flags移動到ecx中
0x00007fffe102205b: mov %edx,%ecx
// 從flags中取出引數大小
0x00007fffe102205d: and $0xff,%ecx
// 獲取到recv,%rcx中儲存的是引數大小,最終計算引數所需要的大小為%rsp+%rcx*8-0x8,
// flags中的引數大小對例項方法來說,已經包括了recv的大小
// 如呼叫例項方法的第一個引數是this(recv)
0x00007fffe1022063: mov -0x8(%rsp,%rcx,8),%rcx // recv儲存到%rcx
// 將flags儲存到r13中
0x00007fffe1022068: mov %edx,%r13d
// 從flags中獲取return type,也就是從_flags的高4位儲存的TosState
0x00007fffe102206b: shr $0x1c,%edx
// 將TemplateInterpreter::invoke_return_entry地址儲存到%r10
0x00007fffe102206e: movabs $0x7ffff73b6380,%r10
// %rdx儲存的是return type,計算返回地址
// 因為TemplateInterpreter::invoke_return_entry是陣列,
// 所以要找到對應return type的入口地址
0x00007fffe1022078: mov (%r10,%rdx,8),%rdx
// 向棧中壓入返回地址
0x00007fffe102207c: push %rdx
// 還原ConstantPoolCacheEntry::_flags
0x00007fffe102207d: mov %r13d,%edx
// 還原bcp
0x00007fffe1022080: mov -0x38(%rbp),%r13
執行完如上的程式碼後,已經向相關的暫存器中儲存了相關的值。相關的暫存器狀態如下:
rbx: 儲存的是ConstantPoolCacheEntry::_f2屬性的值
rcx: 就是呼叫例項方法時的第一個引數this
rdx: 儲存的是ConstantPoolCacheEntry::_flags屬性的值
棧的狀態如下圖所示。
需要注意的是return address也是一個例程的地址,是TemplateInterpreter::invoke\_return\_entry一維陣列中型別為整數對應的下標儲存的那個地址,因為呼叫add()方法返回的是整數型別。如何得出add()方法的返回型別呢?是從ConstantPoolCacheEntry的_flags的TosState中得出的。
下面繼續看invokevirtual位元組碼指令將要執行的彙編程式碼,如下:
// flags儲存到%eax
0x00007fffe1022084: mov %edx,%eax
// 測試呼叫的方法是否為final
0x00007fffe1022086: and $0x100000,%eax
// 如果不為final就直接跳轉到----notFinal----
0x00007fffe102208c: je 0x00007fffe10220c0
// 通過(%rcx)來獲取receiver的值,如果%rcx為空,則會引起OS異常
0x00007fffe1022092: cmp (%rcx),%rax
// 省略統計相關程式碼部分
// 設定呼叫者棧頂並儲存
0x00007fffe10220b4: lea 0x8(%rsp),%r13
0x00007fffe10220b9: mov %r13,-0x10(%rbp)
// 跳轉到Method::_from_interpretered_entry入口去執行
0x00007fffe10220bd: jmpq *0x58(%rbx)
執行Method::\_from\_interpretered_entry例程,這個例程在之前詳細介紹過,執行完成後會為add()方法建立棧幀,此時的棧狀態如下圖所示。
執行iload\_0與iload\_1指令,由於連續出現了2個iload,所以是\_fast\_iload2,彙編如下:
movzbl 0x1(%r13),%ebx
neg %rbx
mov (%r14,%rbx,8),%eax
push %rax
movzbl 0x3(%r13),%ebx
neg %rbx
mov (%r14,%rbx,8),%eax
注意,只有第1個變數壓入了棧,第2個則儲存到%eax中做為棧頂快取。
呼叫iadd指令,由於tos_in為itos,所以彙編如下:
mov (%rsp),%edx
add $0x8,%rsp
add %edx,%eax
最後結果快取在%eax中。
3.退出add()方法
執行ireturn位元組碼指令進行add()方法的退棧操作。對於例項來說,執行的相關彙編程式碼如下:
// 將JavaThread::do_not_unlock_if_synchronized屬性儲存到%dl中
0x00007fffe101b770: mov 0x2ad(%r15),%dl
// 重置JavaThread::do_not_unlock_if_synchronized屬性值為false
0x00007fffe101b777: movb $0x0,0x2ad(%r15)
// 將Method*載入到%rbx中
0x00007fffe101b77f: mov -0x18(%rbp),%rbx
// 將Method::_access_flags載入到%ecx中
0x00007fffe101b783: mov 0x28(%rbx),%ecx
// 檢查Method::flags是否包含JVM_ACC_SYNCHRONIZED
0x00007fffe101b786: test $0x20,%ecx
// 如果方法不是同步方法,跳轉到----unlocked----
0x00007fffe101b78c: je 0x00007fffe101b970
unlocked處的彙編實現如下:
// 將-0x8(%rbp)處儲存的old stack pointer(saved rsp)取出來放到%rbx中
0x00007fffe101bac7: mov -0x8(%rbp),%rbx
// 移除棧幀
// leave指令相當於:
// mov %rbp, %rsp
// pop %rbp
0x00007fffe101bacb: leaveq
// 將返回地址彈出到%r13中
0x00007fffe101bacc: pop %r13
// 設定%rsp為呼叫者的棧頂值
0x00007fffe101bace: mov %rbx,%rsp
0x00007fffe101bad1: jmpq *%r13
執行leaveq指令進行退棧操作,此時的棧狀態如下圖所示。
然後我們就要彈出返回地址,跳轉到TemplateInterpreter::invoke\_return\_entry陣列中儲存的相關地址去執行對應的例程了。
4.執行返回例程
對於例項來說,傳遞的state為itos時生成的彙編程式碼如下:
// 將-0x10(%rbp)儲存到%rsp後,置空-0x10(%rbp)
0x00007fffe1006ce0: mov -0x10(%rbp),%rsp // 更改rsp
0x00007fffe1006ce4: movq $0x0,-0x10(%rbp) // 更改棧中特定位置的值
// 恢復bcp和locals,使%r14指向本地變數表,%r13指向bcp
0x00007fffe1006cec: mov -0x38(%rbp),%r13
0x00007fffe1006cf0: mov -0x30(%rbp),%r14
// 獲取ConstantPoolCacheEntry的索引並載入到%ecx
0x00007fffe1006cf4: movzwl 0x1(%r13),%ecx
// 獲取棧中-0x28(%rbp)的ConstantPoolCache並載入到%ecx
0x00007fffe1006cf9: mov -0x28(%rbp),%rbx
// shl是邏輯左移,獲取字偏移
0x00007fffe1006cfd: shl $0x2,%ecx
// 獲取ConstantPoolCacheEntry中的_flags屬性值
0x00007fffe1006d00: mov 0x28(%rbx,%rcx,8),%ebx
// 獲取_flags中的低8位中儲存的引數大小
0x00007fffe1006d04: and $0xff,%ebx
// lea指令將地址載入到記憶體暫存器中,也就是恢復呼叫方法之前棧的樣子
0x00007fffe1006d0a: lea (%rsp,%rbx,8),%rsp
// 跳轉到下一指令執行
0x00007fffe1006d0e: movzbl 0x3(%r13),%ebx
0x00007fffe1006d13: add $0x3,%r13
0x00007fffe1006d17: movabs $0x7ffff73b7ca0,%r10
0x00007fffe1006d21: jmpq *(%r10,%rbx,8)
如上的彙編程式碼也是執行的退棧操作,最主要的就是把在呼叫解釋執行方法時壓入的實參從棧中彈出,接著就是執行main()方法中invokevirtual中的下一條指令pop。此時的棧狀態如下圖所示。
需要注意的是,此時的棧頂快取中儲存著呼叫add()方法的執行結果,那麼在跳轉到下一條指令pop時,必須要從pop的iep入口進入,這樣就能正確的執行下去了。
5.退出main()方法
當執行pop指令時,會從iep入口進入,執行的彙編程式碼如下:
// iep
push %rax
// ...
add $0x8,%rsp
由於main()方法呼叫add()方法不需要返回結果,所以對於main()方法來說,這個結果會從main()方法的表示式棧中彈出。下面接著執行return指令,這個指令對應的彙編程式碼如下:
// 將JavaThread::do_not_unlock_if_synchronized屬性儲存到%dl中
0x00007fffe101b770: mov 0x2ad(%r15),%dl
// 重置JavaThread::do_not_unlock_if_synchronized屬性值為false
0x00007fffe101b777: movb $0x0,0x2ad(%r15)
// 將Method*載入到%rbx中
0x00007fffe101b77f: mov -0x18(%rbp),%rbx
// 將Method::_access_flags載入到%ecx中
0x00007fffe101b783: mov 0x28(%rbx),%ecx
// 檢查Method::flags是否包含JVM_ACC_SYNCHRONIZED
0x00007fffe101b786: test $0x20,%ecx
// 如果方法不是同步方法,跳轉到----unlocked----
0x00007fffe101b78c: je 0x00007fffe101b970
main()方法為非同步方法,所以跳轉到unlocked,在unlocked邏輯中會執行一些釋放鎖的邏輯,對於我們本例項來說這不重要,我們直接看退棧的操作,如下:
// 將-0x8(%rbp)處儲存的old stack pointer(saved rsp)取出來放到%rbx中
0x00007fffe101bac7: mov -0x8(%rbp),%rbx
// 移除棧幀
// leave指令相當於:
// mov %rbp, %rsp
// pop %rbp
0x00007fffe101bacb: leaveq
// 將返回地址彈出到%r13中
0x00007fffe101bacc: pop %r13
// 設定%rsp為呼叫者的棧頂值
0x00007fffe101bace: mov %rbx,%rsp
0x00007fffe101bad1: jmpq *%r13
最後的棧狀態如下圖所示。
其中的return address是C++語言的返回地址,接下來如何退出如上的一些棧幀及結束方法就是C++的事兒了。