在 第9篇-字節碼指令的定義 我們介紹了字節碼指令並且將字節碼指令相關的信息都存儲到了相關數組中,只需要通過Opcode就可從相關數組中獲取對應的信息。
在init_globals()函數中調用bytecodes_init()函數初始化好字節碼指令后會調用interpreter_init()函數初始化解釋器。函數最終會調用到TemplateInterpreter::initialize()函數。這個函數的實現如下:
源代碼位置:/src/share/vm/interpreter/templateInterpreter.cpp void TemplateInterpreter::initialize() { if (_code != NULL) return; // 抽象解釋器AbstractInterpreter的初始化, // AbstractInterpreter是基於匯編模型的解釋器的共同基類, // 定義了解釋器和解釋器生成器的抽象接口 AbstractInterpreter::initialize(); // 模板表TemplateTable的初始化,模板表TemplateTable保存了各個字節碼的模板 TemplateTable::initialize(); // generate interpreter { ResourceMark rm; int code_size = InterpreterCodeSize; // CodeCache的Stub隊列StubQueue的初始化 _code = new StubQueue(new InterpreterCodeletInterface, code_size, NULL,"Interpreter"); // 實例化模板解釋器生成器對象TemplateInterpreterGenerator InterpreterGenerator g(_code); } // 初始化字節分發表 _active_table = _normal_table; }
這個初始化函數中涉及到的初始化邏輯比較多,而且比較復雜。我們將初始化分為4部分:
(1)抽象解釋器AbstractInterpreter的初始化,AbstractInterpreter是基於匯編模型的解釋器的共同基類,定義了解釋器和解釋器生成器的抽象接口。
(2)模板表TemplateTable的初始化,模板表TemplateTable保存了各個字節碼的模板(目標代碼生成函數和參數);
(3)CodeCache的Stub隊列StubQueue的初始化;
(4)解釋器生成器InterpreterGenerator的初始化。
其中抽象解釋器初始化時會涉及到一些計數,這些計數主要與編譯執行有關,所以這里暫不過多介紹,到后面介紹編譯執行時再介紹。
下面我們分別介紹如上3個部分的初始化過程,這一篇只介紹模板表的初始化過程。
函數TemplateTable::initialize()的實現如下:
模板表TemplateTable保存了各個字節碼的執行模板(目標代碼生成函數和參數),而前一篇介紹對字節碼的定義已經進行了詳細介紹,執行模板定義的是每個字節碼如何在解釋模式下執行的。initialize()函數的實現如下:
源代碼位置:/src/share/vm/interpreter/templateInterpreter.cpp void TemplateTable::initialize() { if (_is_initialized) return; _bs = Universe::heap()->barrier_set(); // For better readability const char _ = ' '; const int ____ = 0; const int ubcp = 1 << Template::uses_bcp_bit; const int disp = 1 << Template::does_dispatch_bit; const int clvm = 1 << Template::calls_vm_bit; const int iswd = 1 << Template::wide_bit; // interpr. templates // Java spec bytecodes ubcp|disp|clvm|iswd in out generator argument def(Bytecodes::_nop , ____|____|____|____, vtos, vtos, nop , _ ); def(Bytecodes::_aconst_null , ____|____|____|____, vtos, atos, aconst_null , _ ); def(Bytecodes::_iconst_m1 , ____|____|____|____, vtos, itos, iconst , -1 ); def(Bytecodes::_iconst_0 , ____|____|____|____, vtos, itos, iconst , 0 ); // ... def(Bytecodes::_tableswitch , ubcp|disp|____|____, itos, vtos, tableswitch , _ ); def(Bytecodes::_lookupswitch , ubcp|disp|____|____, itos, itos, lookupswitch , _ ); 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::_getstatic , ubcp|____|clvm|____, vtos, vtos, getstatic , f1_byte ); def(Bytecodes::_putstatic , ubcp|____|clvm|____, vtos, vtos, putstatic , f2_byte ); def(Bytecodes::_getfield , ubcp|____|clvm|____, vtos, vtos, getfield , f1_byte ); def(Bytecodes::_putfield , ubcp|____|clvm|____, vtos, vtos, putfield , f2_byte ); def(Bytecodes::_invokevirtual , ubcp|disp|clvm|____, vtos, vtos, invokevirtual , f2_byte ); def(Bytecodes::_invokespecial , ubcp|disp|clvm|____, vtos, vtos, invokespecial , f1_byte ); def(Bytecodes::_invokestatic , ubcp|disp|clvm|____, vtos, vtos, invokestatic , f1_byte ); def(Bytecodes::_invokeinterface , ubcp|disp|clvm|____, vtos, vtos, invokeinterface , f1_byte ); def(Bytecodes::_invokedynamic , ubcp|disp|clvm|____, vtos, vtos, invokedynamic , f1_byte ); def(Bytecodes::_new , ubcp|____|clvm|____, vtos, atos, _new , _ ); def(Bytecodes::_newarray , ubcp|____|clvm|____, itos, atos, newarray , _ ); def(Bytecodes::_anewarray , ubcp|____|clvm|____, itos, atos, anewarray , _ ); def(Bytecodes::_arraylength , ____|____|____|____, atos, itos, arraylength , _ ); def(Bytecodes::_athrow , ____|disp|____|____, atos, vtos, athrow , _ ); def(Bytecodes::_checkcast , ubcp|____|clvm|____, atos, atos, checkcast , _ ); def(Bytecodes::_instanceof , ubcp|____|clvm|____, atos, itos, instanceof , _ ); def(Bytecodes::_monitorenter , ____|disp|clvm|____, atos, vtos, monitorenter , _ ); def(Bytecodes::_monitorexit , ____|____|clvm|____, atos, vtos, monitorexit , _ ); def(Bytecodes::_wide , ubcp|disp|____|____, vtos, vtos, wide , _ ); def(Bytecodes::_multianewarray , ubcp|____|clvm|____, vtos, atos, multianewarray , _ ); def(Bytecodes::_ifnull , ubcp|____|clvm|____, atos, vtos, if_nullcmp , equal ); def(Bytecodes::_ifnonnull , ubcp|____|clvm|____, atos, vtos, if_nullcmp , not_equal ); def(Bytecodes::_goto_w , ubcp|____|clvm|____, vtos, vtos, goto_w , _ ); def(Bytecodes::_jsr_w , ubcp|____|____|____, vtos, vtos, jsr_w , _ ); // wide Java spec bytecodes def(Bytecodes::_iload , ubcp|____|____|iswd, vtos, itos, wide_iload , _ ); def(Bytecodes::_lload , ubcp|____|____|iswd, vtos, ltos, wide_lload , _ ); // ... // JVM bytecodes // ... def(Bytecodes::_shouldnotreachhere , ____|____|____|____, vtos, vtos, shouldnotreachhere , _ ); }
TemplateTable的初始化調用def()將所有字節碼的目標代碼生成函數和參數保存在_template_table或_template_table_wide(wide指令)模板數組中。除了虛擬機規范本身定義的字節碼指令外,HotSpot虛擬機也定義了一些字節碼指令,這些指令為了輔助虛擬機進行更好的功能實現,例如Bytecodes::_return_register_finalizer等在之前已經介紹過,可以更好的實現finalizer類型對象的注冊功能。
我們只給出部分字節碼指令的模板定義,調用def()函數對每個字節碼指令的模板進行定義,傳遞的參數是我們關注的重點:
(1)指出為哪個字節碼指令定義模板
(2)ubcp|disp|clvm|iswd,這是一個組合數字,具體的數字與Template中定義的枚舉類緊密相關,枚舉類中定義的常量如下:
enum Flags { uses_bcp_bit, // set if template needs the bcp pointing to bytecode does_dispatch_bit, // set if template dispatches on its own 就其本身而言; 靠自己 calls_vm_bit, // set if template calls the vm wide_bit // set if template belongs to a wide instruction };
下面詳細解釋這幾個參數,如下:
- uses_bcp_bit,標志需要使用字節碼指針(byte code pointer,數值為字節碼基址+字節碼偏移量)。表示生成的模板代碼中是否需要使用指向字節碼指令的指針,其實也就是說是否需要讀取字節碼指令的操作數,所以含有操作數的指令大部分都需要bcp,但是有一些是不需要的,如monitorenter與monitorexit等,這些的操作數都在表達式棧中,表達式棧頂就是其操作數,並不需要從Class文件中讀取,所以不需要bcp;
- does_dispatch_bit,標志表示自己是否含有控制流轉發邏輯,如tableswitch、lookupswitch、invokevirtual、ireturn等字節碼指令,本身就需要進行控制流轉發;
- calls_vm_bit,標志是否需要調用JVM函數,在調用TemplateTable::call_VM()函數時都會判斷是否有這個標志,通常方法調用JVM函數時都會通過調用TemplateTable::call_VM()函數來間接完成調用。JVM函數就是用C++寫的函數。
- wide_bit,標志是否是wide指令(使用附加字節擴展全局變量索引)
(3)_tos_in與_tos_out:表示模板執行前與模板執行后的TosState(操作數棧棧頂元素的數據類型,TopOfStack,用來檢查模板所聲明的輸出輸入類型是否和該函數一致,以確保棧頂元素被正確使用)。
_tos_in與_tos_out的值必須是枚舉類中定義的常量,如下:
enum TosState { // describes the tos cache contents btos = 0, // byte, bool tos cached ctos = 1, // char tos cached stos = 2, // short tos cached itos = 3, // int tos cached ltos = 4, // long tos cached ftos = 5, // float tos cached dtos = 6, // double tos cached atos = 7, // object cached vtos = 8, // tos not cached number_of_states, ilgl // illegal state: should not occur };
如iload指令,執行之前棧頂狀態為vtos,表示並不會使用棧頂的數據,所以如果程序為了提高執行效率將上一次執行的結果緩存到了寄存器中,那么此時就應該在執行iload指令之前將這個寄存器的值壓入棧頂。iload指令執行之后的棧頂狀態為itos,因為iload是向操作數棧中壓入一個整數,所以此時的棧頂狀態為int類型,那么這個值可以緩存到寄存器中,假設下一個指令為ireturn,那么棧頂之前與之后的狀態分別為itos和itos,那么可直接將緩存在寄存器中的int類型返回即可,不需要做任何和操作數棧相關的操作。
(4)_gen與_arg:_gen表示模板生成器(函數指針),這個函數會為對應的字節碼生成對應的執行邏輯;_arg表示為模板生成器傳遞的參數。調用函數指針會為每個字節碼指令按其語義針對不同的平台上生成不同的機器指令,這里我們只討論x86架構下64位的機器指令實現,由於機器指令很難讀懂,所以我們后續只閱讀由機器指令反編譯的匯編指令。
下面看一下TemplateTable::initialize()函數中調用的Template::def()函數,如下:
void TemplateTable::def( Bytecodes::Code code, // 字節碼指令 int flags, // 標志位 TosState in, // 模板執行前TosState TosState out, // 模板執行后TosState void (*gen)(int arg), // 模板生成器,是模板的核心組件 int arg ) { // 表示是否需要bcp指針 const int ubcp = 1 << Template::uses_bcp_bit; // 表示是否在模板范圍內進行轉發 const int disp = 1 << Template::does_dispatch_bit; // 表示是否需要調用JVM函數 const int clvm = 1 << Template::calls_vm_bit; // 表示是否為wide指令 const int iswd = 1 << Template::wide_bit; // 如果是允許在字節碼指令前加wide字節碼指令的一些指令,那么 // 會使用_template_table_wild模板數組進行字節碼轉發,否則 // 使用_template_table模板數組進行轉發 bool is_wide = (flags & iswd) != 0; Template* t = is_wide ? template_for_wide(code) : template_for(code); // 調用模板表t的initialize()方法初始化模板表 t->initialize(flags, in, out, gen, arg); }
模板表由模板表數組與一組生成器組成:
模板數組有_template_table與_template_table_wild,數組的下標為字節碼的Opcode,值為Template。定義如下:
Template TemplateTable::_template_table[Bytecodes::number_of_codes]; Template TemplateTable::_template_table_wide[Bytecodes::number_of_codes];
模板數組的值為Template,這個Template類中定義了保存標志位flags的_flags屬性,保存棧頂緩存狀態in和out的_tos_in和_tos_out,還有保存生成器gen及參數arg的_gen與_arg,所以調用t->initialize()后其實是初始化Template中的變量。initialize()函數的實現如下:
void Template::initialize( int flags, TosState tos_in, TosState tos_out, generator gen, int arg ) { _flags = flags; _tos_in = tos_in; _tos_out = tos_out; _gen = gen; _arg = arg; }
不過這里並不會調用gen函數生成對應的匯編代碼,只是將傳遞給def()函數的各種信息保存到Template實例中,在TemplateTable::def()函數中,通過template_for()或template_for_wild()函數獲取到數組中對應的Template實例后,就會調用Template::initialize()函數將信息保存到對應的Template實例中,這樣就可以根據字節碼索引從數組中獲取對應的Template實例,進而獲取字節碼指令模板的相關信息。
雖然這里並不會調用gen來生成字節碼指令對應的機器指令,但是我們可以提前看一下gen這個指針函數是怎么為某個字節碼指令生成對應的機器指令的。
看一下TemplateTable::initialize()函數中對def()函數的調用,以_iinc(將局部變量表中對應的slot位的值增加1)為例,調用如下:
def( Bytecodes::_iinc, // 字節碼指令 ubcp|____|clvm|____, // 標志 vtos, // 模板執行前的TosState vtos, // 模板執行后的TosState iinc , // 模板生成器,是一個iinc()函數的指針 _ // 不需要模板生成器參數 );
設置標志位uses_bcp_bit和calls_vm_bit,表示iinc指令的生成器需要使用bcp指針函數at_bcp(),且需要調用JVM函數,下面給出了生成器的定義:
源代碼位置:/hotspot/src/cpu/x86/vm/templateTable_x86_64.cpp void TemplateTable::iinc() { transition(vtos, vtos); __ load_signed_byte(rdx, at_bcp(2)); // get constant locals_index(rbx); __ addl(iaddress(rbx), rdx); }
由於iinc指令只涉及到對局部變量表的操作,並不會影響操作數棧,也不需要使用操作數棧頂的值,所以棧頂之前與之后的狀態為vtos與vtos,調用transition()函數只是驗證棧頂緩存的狀態是否正確。
iinc指令的字節碼格式如下:
iinc index // 局部變量表索引值 const // 將局部變量表索引值對應的slot值加const
操作碼iinc占用一個字節,而index與const分別占用一個字節。使用at_bcp()函數獲取iinc指令的操作數,2表示偏移2字節,所以會將const取出來存儲到rdx中。調用locals_index()函數取出index,locals_index()就是JVM函數。最終生成的匯編如下:
// %r13存儲的是指向字節碼的指針,偏移 // 2字節后取出const存儲到%edx movsbl 0x2(%r13),%edx // 取出index存儲到%ebx movzbl 0x1(%r13),%ebx neg %rbx // %r14指向本地變量表的首地址,將%edx加到 // %r14+%rbx*8指向的內存所存儲的值上 // 之所以要對%rbx執行neg進行符號反轉, // 是因為在Linux內核的操作系統上, // 棧是向低地址方向生長的 add %edx,(%r14,%rbx,8)
注釋解釋的已經非常清楚了,這里不再過多介紹。
推薦閱讀:
第2篇-JVM虛擬機這樣來調用Java主類的main()方法
如果有問題可直接評論留言或加作者微信mazhimazh
關注公眾號,有HotSpot VM源碼剖析系列文章!