在 第6篇-Java方法新棧幀的建立 介紹過區域性變量表的建立,建立完成後的棧幀狀態如下圖所示。

各個暫存器的狀態如下所示。

// %rax暫存器中儲存的是返回地址
rax: return address
// 要執行的Java方法的指標
rbx: Method*
// 本地變量表指標
r14: pointer to locals
// 呼叫者的棧頂
r13: sender sp

注意rax中儲存的返回地址,因為在generate_call_stub()函式中通過__ call(c_rarg1) 語句呼叫了由generate_normal_entry()函式生成的entry_point,所以當entry_point執行完成後,還會返回到generate_call_stub()函式中繼續執行__ call(c_rarg1) 語句下面的程式碼,也就是

第5篇-呼叫Java方法後彈出棧幀及處理返回結果  涉及到的那些程式碼。

呼叫的generate_fixed_frame()函式的實現如下:

原始碼位置:src/cpu/x86/vm/templateInterpreter_x86_64.cpp

void TemplateInterpreterGenerator::generate_fixed_frame(bool native_call) {
// 把返回地址緊接著區域性變數區儲存
__ push(rax);
// 為Java方法建立棧幀
__ enter();
// 儲存呼叫者的棧頂地址
__ push(r13);
// 暫時將last_sp屬性的值設定為NULL_WORD
__ push((int)NULL_WORD);
// 獲取ConstMethod*並儲存到r13中
__ movptr(r13, Address(rbx, Method::const_offset()));
// 儲存Java方法位元組碼的地址到r13中
__ lea(r13, Address(r13, ConstMethod::codes_offset()));
// 儲存Method*到堆疊上
__ push(rbx); // ProfileInterpreter屬性的預設值為true,
// 表示需要對解釋執行的方法進行相關資訊的統計
if (ProfileInterpreter) {
Label method_data_continue;
// MethodData結構基礎是ProfileData,
// 記錄函式執行狀態下的資料
// MethodData裡面分為3個部分,
// 一個是函式型別等執行相關統計資料,
// 一個是引數型別執行相關統計資料,
// 還有一個是extra擴充套件區儲存著
// deoptimization的相關資訊
// 獲取Method中的_method_data屬性的值並儲存到rdx中
__ movptr(rdx, Address(rbx,
in_bytes(Method::method_data_offset())));
__ testptr(rdx, rdx);
__ jcc(Assembler::zero, method_data_continue);
// 執行到這裡,說明_method_data已經進行了初始化,
// 通過MethodData來獲取_data屬性的值並存儲到rdx中
__ addptr(rdx, in_bytes(MethodData::data_offset()));
__ bind(method_data_continue);
__ push(rdx);
} else {
__ push(0);
} // 獲取ConstMethod*儲存到rdx
__ movptr(rdx, Address(rbx,
Method::const_offset()));
// 獲取ConstantPool*儲存到rdx
__ movptr(rdx, Address(rdx,
ConstMethod::constants_offset()));
// 獲取ConstantPoolCache*並存儲到rdx
__ movptr(rdx, Address(rdx,
ConstantPool::cache_offset_in_bytes()));
// 儲存ConstantPoolCache*到堆疊上
__ push(rdx);
// 儲存第1個引數的地址到堆疊上
__ push(r14); if (native_call) {
// native方法呼叫時,不需要儲存Java
// 方法的位元組碼地址,因為沒有位元組碼
__ push(0);
} else {
// 儲存Java方法位元組碼地址到堆疊上,
// 注意上面對r13暫存器的值進行了更改
__ push(r13);
} // 預先保留一個slot,後面有大用處
__ push(0);
// 將棧底地址儲存到這個slot上
__ movptr(Address(rsp, 0), rsp);
}

對於普通的Java方法來說,生成的彙編程式碼如下:  

push   %rax
push %rbp
mov %rsp,%rbp
push %r13
pushq $0x0
mov 0x10(%rbx),%r13
lea 0x30(%r13),%r13 // lea指令獲取記憶體地址本身
push %rbx
mov 0x18(%rbx),%rdx
test %rdx,%rdx
je 0x00007fffed01b27d
add $0x90,%rdx
push %rdx
mov 0x10(%rbx),%rdx
mov 0x8(%rdx),%rdx
mov 0x18(%rdx),%rdx
push %rdx
push %r14
push %r13
pushq $0x0
mov %rsp,(%rsp)

彙編比較簡單,這裡不再多說。執行完如上的彙編後生成的棧幀狀態如下圖所示。

 

呼叫完generate_fixed_frame()函式後一些暫存器中儲存的值如下:

rbx:Method*
ecx:invocation counter
r13:bcp(byte code pointer)
rdx:ConstantPool* 常量池的地址
r14:本地變量表第1個引數的地址

執行完generate_fixed_frame()函式後會繼續返回執行InterpreterGenerator::generate_normal_entry()函式,如果是為同步方法生成機器碼,那麼還需要呼叫lock_method()函式,這個函式會改變當前棧幀的狀態,新增同步所需要的一些資訊,在後面介紹鎖的實現時會詳細介紹。

InterpreterGenerator::generate_normal_entry()函式最終會返回生成機器碼的入口執行地址,然後通過變數_entry_table陣列來儲存,這樣就可以使用方法型別做為陣列下標獲取對應的方法入口了。 

推薦閱讀:

第1篇-關於JVM執行時,開篇說的簡單些

第2篇-JVM虛擬機器這樣來呼叫Java主類的main()方法

第3篇-CallStub新棧幀的建立

第4篇-JVM終於開始呼叫Java主類的main()方法啦

第5篇-呼叫Java方法後彈出棧幀及處理返回結果

第6篇-Java方法新棧幀的建立

如果有問題可直接評論留言或加作者微信mazhimazh

關注公眾號,有HotSpot原始碼剖析系列文章!