衆所周知,hotspot默認使用解釋+編譯混合
(-Xmixed)的方式執行代碼。它首先使用模板解釋器對字節碼進行解釋,當發現一段代碼是熱點的時候,就使用C1/C2 JIT進行優化編譯再執行,這也它的名字"熱點"(hotspot)的由來。java
解釋器的代碼位於hotspot/share/interpreter
,它的整體架構以下:express
首先hotspot有一個C++字節碼解釋器,還有一個模板解釋器 ,默認使用的是模板解釋器的實現。這兩個有什麼區別呢?舉個例子,Java字節碼有istore_0
,iadd
,若是是C++字節碼解釋器(圖右部分),那麼它的工做流程就是這種:數組
void cppInterpreter::work(){ for(int i=0;i<bytecode.length();i++){ switch(bytecode[i]){ case ISTORE_0: int value = operandStack.pop(); localVar[0] = value; break; case IADD: int v1 = operandStack.pop(); int v2 = operandStack.pop(); int res = v1+v2; operandStack.push(res); break; .... } } }
它使用C++語言模擬字節碼的執行,iadd是兩個數相加,字節碼解釋器從棧上pop兩個數據而後求和,再push到棧上。安全
若是是模板解釋器就徹底不同了。模板解釋器是一堆本地碼的例程(routines),它會在虛擬機建立的時候初始化好,也就是說,模板解釋器在初始化的時候會申請一片內存並設置爲可讀可寫可執行,而後向那片內存寫入本地碼。在解釋執行的時候遇到iadd,就執行那片內存裏面的二進制代碼。架構
這種運行時代碼生成的機制能夠說是JIT,只是一般意義的JIT是指對一塊代碼進行優化再生成本地代碼,同一段代碼可能由於分紅編譯產出不一樣的本地碼,具備動態性;而模板解釋器是虛擬機在建立的時候JIT生成它自身,它的每一個例程好比異常處理部分,安全點處理部分的本地碼都是固定的,是靜態的。佈局
再回到主題,架構圖有一個抽象解釋器,這個抽象解釋器描述瞭解釋器的基本骨架,它的屬性以下:優化
class AbstractInterpreter{ StubQueue* _code address _slow_signature_handler; address _entry_table[n]; address _cds_entry_table[n]; ... };
全部的解釋器(C++字節碼解釋器,模板解釋器)都有這些例程和屬性,而後子類的解釋器還能夠再擴展一些例程。操作系統
咱們重點關注_code
,它是一個隊列,設計
隊列中的InterpreterCodelet表示一個小例程,好比iconst_1對應的代碼,invokedynamic對應的代碼,異常處理對應的代碼,方法入口點對應的代碼,這些代碼都是一個個InterpreterCodelet...整個解釋器都是由這些小塊代碼例程組成的,每一個小塊例程完成解釋器的部分功能,以此實現整個解釋器。指針
_entry_table
也是個重要的屬性,這個數組表示方法的例程,好比普通方法是入口點1_entry_table[0]
,帶synchronized的方法是入口點2_entry_table[1]
,這些_entry_table[0],_entry_table[1]
指向的就是以前_code隊列裏面的小塊例程,就像這樣:
_entry_table[0] = _code->get_stub("iconst_1")->get_address(); _entry_table[1] = _code->get_stub("fconst_1")->get_address();
固然實際的實現遠比僞代碼複雜。
前面說道小塊例程組合起來實現瞭解釋器,抽象解釋器定義了必要的例程,具體的解釋器在這之上還有本身的特設的例程。模板解釋器就是一個例子,它繼承自抽象解釋器,在那些例程之上還有本身的特設例程:
// 數組越界異常例程 static address _throw_ArrayIndexOutOfBoundsException_entry; // 數組存儲異常例程 static address _throw_ArrayStoreException_entry; // 算術異常例程 static address _throw_ArithmeticException_entry; // 類型轉換異常例程 static address _throw_ClassCastException_entry; // 空指針異常例程 static address _throw_NullPointerException_entry; // 拋異常公共例程 static address _throw_exception_entry;
這樣作的好處是能夠針對一些特殊例程進行特殊處理,同時還能夠複用代碼。
到這裏解釋器的佈局應該是說清楚了,咱們大概知道了:解釋器是一堆本地代碼例程構造的,這些例程會在虛擬機啓動的時候寫入,之後解釋就只須要進入指定例程便可。
還有一個問題,這些例程是誰寫入的呢?找一找架構圖,下半部分都是解釋器生成器,它的名字也是自解釋的,那麼它就是答案了。
前面刻意說道解釋器佈局就是想突出它只是一個骨架,要獲得可運行的解釋器還須要解釋器生成器填充這個骨架。
解釋器生成器原本能夠獨自完成填充工做,可能爲了解耦,也多是爲告終構清晰,hotspot將字節碼的例程抽了出來放到了templateTable(模板表)中,它輔助模板解釋器生成器(templateInterpreterGenerator)完成各例程填充。
只有這兩個還不能完成任務,由於組成模板解釋器的是本地代碼例程,本地代碼例程依賴於操做系統和CPU,這部分代碼位於hotspot/cpu/x86/
中,因此
templateInterpreter = templateTable + templateTable_x86 + templateInterpreterGenerator + templateInterpreterGenerator_x86 + templateInterpreterGenerator_x86_64
虛擬機中有不少這樣的設計:在hotspot/share/
的某個頭文件寫出定義,在源文件實現OS/CPU無關的代碼,而後在hotspot/cpu/x86
中實現CPU相關的代碼,在hostpot/os
實現OS相關的代碼。
這麼說可能有些蒼白無力,仍是結合代碼更具說服力。
模板解釋器擴展了抽象解釋器,它有一個數組越界異常例程:
// 解釋器生成器 // hotspot\share\interpreter\templateInterpreterGenerator.cpp void TemplateInterpreterGenerator::generate_all() { ... { CodeletMark cm(_masm, "throw exception entrypoints"); // 調用CPU相關的代碼生成例程 Interpreter::_throw_ArrayIndexOutOfBoundsException_entry = generate_ArrayIndexOutOfBounds_handler(); } ... } // 解釋器生成器中CPU相關的部分 // hotspot\os\x86\templateInterpreterGenerator_x86.cpp address TemplateInterpreterGenerator::generate_ArrayIndexOutOfBounds_handler() { address entry = __ pc(); __ empty_expression_stack(); // rarg是數組越界的對象,rbx是越界的索引 Register rarg = NOT_LP64(rax) LP64_ONLY(c_rarg1); __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime:: throw_ArrayIndexOutOfBoundsException), rarg, rbx); return entry; } // 解釋器運行時 // hotspot\share\interpreter\interpreterRuntime.cpp IRT_ENTRY(void, InterpreterRuntime::throw_ArrayIndexOutOfBoundsException(JavaThread* thread, arrayOopDesc* a, jint index)) ResourceMark rm(thread); stringStream ss; ss.print("Index %d out of bounds for length %d", index, a->length()); THROW_MSG(vmSymbols::java_lang_ArrayIndexOutOfBoundsException(), ss.as_string()); IRT_END
解釋器生成器會調用CPU相關的generate_ArrayIndexOutOfBounds_handler()生成異常處理例程,裏面有個call_VM
,它調用了解釋器運行時(InterpreterRuntime)來處理異常。解釋器運行時是C++代碼,
之因此用它是由於異常處理比較麻煩,還須要C++其餘模塊的支持(好比這裏的stringStream和THROW_MSG),直接生成機器碼會很是麻煩,咱們能夠調用解釋器運行時相對輕鬆的處理。
咱們在後面還會常常遇到call_VM
調用解釋器運行時這種模式,若是有很複雜的任務,須要其餘C++模塊的支持,那麼它就派上用場了。