在generate_normal_entry()函式中會呼叫generate_fixed_frame()函式為Java方法的執行生成對應的棧幀,接下來還會呼叫dispatch_next()函式執行Java方法的位元組碼。generate_normal_entry()函式呼叫的dispatch_next()函式之前一些暫存器中儲存的值如下:
rbx:Method*
ecx:invocation counter
r13:bcp(byte code pointer)
rdx:ConstantPool* 常量池的地址
r14:本地變量表第1個引數的地址
dispatch_next()函式的實現如下:
// 從generate_fixed_frame()函式生成Java方法呼叫棧幀的時候,
// 如果當前是第一次呼叫,那麼r13指向的是位元組碼的首地址,
// 即第一個位元組碼,此時的step引數為0
void InterpreterMacroAssembler::dispatch_next(TosState state, int step) { load_unsigned_byte(rbx, Address(r13, step)); // 在當前位元組碼的位置,指標向前移動step寬度,
// 獲取地址上的值,這個值是Opcode(範圍1~202),儲存到rbx
// step的值由位元組碼指令和它的運算元共同決定
// 自增r13供下一次位元組碼分派使用
increment(r13, step); // 返回當前棧頂狀態的所有位元組碼入口點
dispatch_base(state, Interpreter::dispatch_table(state));
}
r13指向位元組碼的首地址,當第1次呼叫時,引數step的值為0,那麼load_unsigned_byte()函式從r13指向的記憶體中取一個位元組的值,取出來的是位元組碼指令的操作碼。增加r13的步長,這樣下次執行時就會取出來下一個位元組碼指令的操作碼。
呼叫的dispatch_table()函式的實現如下:
static address* dispatch_table(TosState state) {
return _active_table.table_for(state);
}
在_active_table中獲取對應棧頂快取狀態的入口地址,_active_table變數定義在TemplateInterpreter類中,如下:
static DispatchTable _active_table;
DispatchTable類及table_for()等函式的定義如下:
DispatchTable TemplateInterpreter::_active_table; class DispatchTable VALUE_OBJ_CLASS_SPEC {
public:
enum {
length = 1 << BitsPerByte
}; // BitsPerByte的值為8 private:
// number_of_states=9,length=256
// _table是位元組碼分發表
address _table[number_of_states][length]; public:
// ...
address* table_for(TosState state){
return _table[state];
} address* table_for(){
return table_for((TosState)0);
}
// ...
};
address為u_char*型別的別名。_table是一個二維陣列的表,維度為棧頂狀態(共有9種)和位元組碼(最多有256個),儲存的是每個棧頂狀態對應的位元組碼的入口點。這裡由於還沒有介紹棧頂快取,所以理解起來並不容易,不過後面會詳細介紹棧頂快取和位元組碼分發表的相關內容,等介紹完了再看這部分邏輯就比較容易理解了。
InterpreterMacroAssembler::dispatch_next()函式中呼叫的dispatch_base()函式的實現如下:
void InterpreterMacroAssembler::dispatch_base(
TosState state, // 表示棧頂快取狀態
address* table,
bool verifyoop
) {
// ...
// 獲取當前棧頂狀態位元組碼轉發表的地址,儲存到rscratch1
lea(rscratch1, ExternalAddress((address)table));
// 跳轉到位元組碼對應的入口執行機器碼指令
// address = rscratch1 + rbx * 8
jmp(Address(rscratch1, rbx, Address::times_8));
}
比如取一個位元組大小的指令(如iconst_0、aload_0等都是一個位元組大小的指令),那麼InterpreterMacroAssembler::dispatch_next()函式生成的彙編程式碼如下 :
// 在generate_fixed_frame()函式中
// 已經讓%r13儲存了bcp
// %ebx中儲存的是位元組碼的Opcode,也就是操作碼
movzbl 0x0(%r13),%ebx // $0x7ffff73ba4a0這個地址指向的
// 是對應state狀態下的一維陣列,長度為256
movabs $0x7ffff73ba4a0,%r10 // 注意%r10中儲存的是常量,根據計算公式
// %r10+%rbx*8來獲取指向儲存入口地址的地址,
// 通過*(%r10+%rbx*8)獲取到入口地址,
// 然後跳轉到入口地址執行
jmpq *(%r10,%rbx,8)
%r10指向的是對應棧頂快取狀態state下的一維陣列,長度為256,其中儲存的值為opcode,如下圖所示。
下面的函式顯示了對每個位元組碼的每個棧頂狀態都設定入口地址。
void DispatchTable::set_entry(int i, EntryPoint& entry) {
assert(0 <= i && i < length, "index out of bounds");
assert(number_of_states == 9, "check the code below");
_table[btos][i] = entry.entry(btos);
_table[ctos][i] = entry.entry(ctos);
_table[stos][i] = entry.entry(stos);
_table[atos][i] = entry.entry(atos);
_table[itos][i] = entry.entry(itos);
_table[ltos][i] = entry.entry(ltos);
_table[ftos][i] = entry.entry(ftos);
_table[dtos][i] = entry.entry(dtos);
_table[vtos][i] = entry.entry(vtos);
}
其中的引數i就是opcode,各個位元組碼及對應的opcode可參考https://docs.oracle.com/javase/specs/jvms/se8/html/index.html。
所以_table表如下圖所示。
_table的一維為棧頂快取狀態,二維為Opcode,通過這2個維度能夠找到一段機器指令,這就是根據當前的棧頂快取狀態定位到的位元組碼需要執行的機器指令片段。
呼叫dispatch_next()函式執行Java方法的位元組碼,其實就是根據位元組碼找到對應的機器指令片段的入口地址來執行,這段機器碼就是根據對應的位元組碼語義翻譯過來的,這些都會在後面詳細介紹。
推薦閱讀:
第2篇-JVM虛擬機器這樣來呼叫Java主類的main()方法
如果有問題可直接評論留言或加作者微信mazhimazh
關注公眾號,有HotSpot原始碼剖析系列文章!