我們在之前介紹過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];
address TemplateInterpreter::_invokedynamic_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)
如上彙編代碼的邏輯非常簡單,這裡不再過多介紹。
公衆号 深入剖析Java虛拟機HotSpot 已經更新虛拟機源代碼剖析相關文章到60+,歡迎關注,如果有任何問題,可加作者微信mazhimazh,拉你入虛拟機群交流