通過《編譯原理》系列文章,我們可以創造出運行環境,然後根據程序的語義直接執行,也可能翻譯成中間代碼(機器碼,彙編碼)。這兩種方式分別被稱爲解釋執行和編譯執行。
JVM中的編譯器
在部分商用虛擬機中(如HotSpot),Java程序最初是通過解釋器(Interpreter)進行解釋執行的,當虛擬機發現某個方法或代碼塊的運行特別頻繁時,就會把這些代碼認定爲“熱點代碼”。爲了提高熱點代碼的執行效率,在運行時,虛擬機將會把這些代碼編譯成與本地平臺相關的機器碼,並進行各種層次的優化,完成這個任務的編譯器稱爲即時編譯器(Just In Time Compiler,下文統稱JIT編譯器)。
爲何HotSpot虛擬器要解釋器和編譯器並存?
儘管並不是所有的Java虛擬機都採用解釋器與編譯器並存的架構,但許多主流的商用虛擬機(如HotSpot),都同時包含解釋器和編譯器。解釋器與編譯器兩者各有優勢:當程序需要迅速啓動和執行的時候,解釋器可以首先發揮作用,省去編譯的時間,立即執行。在程序運行後,隨着時間的推移,編譯器逐漸發揮作用,把越來越多的代碼編譯成本地代碼之後,可以獲取更高的執行效率。當程序運行環境中內存資源限制較大(如部分嵌入式系統中),可以使用解釋器執行節約內存,反之可以使用編譯執行來提升效率。此外,如果編譯後出現“罕見陷阱”,可以通過逆優化退回到解釋執行。
解釋器的執行,抽象的看是這樣的:
輸入的代碼 -> [ 解釋器 解釋執行 ] -> 執行結果
而要JIT編譯然後再執行的話,抽象的看則是:
輸入的代碼 -> [ 編譯器 編譯 ] -> 編譯後的代碼 -> [ 執行 ] -> 執行結果
說JIT比解釋快,其實說的是“執行編譯後的代碼”比“解釋器解釋執行”要快,並不是說“編譯”這個動作比“解釋”這個動作快。JIT編譯再怎麼快,至少也比解釋執行一次略慢一些,而要得到最後的執行結果還得再經過一個“執行編譯後的代碼”的過程。所以,對“只執行一次”的代碼而言,解釋執行其實總是比JIT編譯執行要快。怎麼算是“只執行一次的代碼”呢?粗略說,下面兩個條件同時滿足時就是嚴格的“只執行一次”
- 只被調用一次,例如類的構造器(class initializer,<clinit>())
- 沒有循環
對只執行一次的代碼做JIT編譯再執行,可以說是得不償失。對只執行少量次數的代碼,JIT編譯帶來的執行速度的提升也未必能抵消掉最初編譯帶來的開銷。只有對頻繁執行的代碼,JIT編譯才能保證有正面的收益。
對一般的Java方法而言,編譯後代碼的大小相對於字節碼的大小,膨脹比達到10x是很正常的。同上面說的時間開銷一樣,這裏的空間開銷也是,只有對執行頻繁的代碼才值得編譯,如果把所有代碼都編譯則會顯著增加代碼所佔空間,導致“代碼爆炸”。這也就解釋了爲什麼有些JVM會選擇不總是做JIT編譯,而是選擇用解釋器+JIT編譯器的混合執行引擎。
- 解釋模式:可通過-Xint指定,讓JVM以解釋模式運行Java程序
- 編譯模式:可通過-Xcomp指定,讓JVM以編譯模式運行Java程序
- 混合模式(默認):可能通過-Xmixed指定,讓JVM以解釋+編譯模式運行Java程序
解釋器
在hotspot中,Interpreter是對外的一個解釋器的包裝類,通過宏定義的方式決定使用CppInterpreter(c++解釋器)或者TemplateInterpreter(模板解釋器)。其中模板解釋器爲默認解釋器。下文相關介紹均以TemplateInterpreter及x86_64爲前提介紹。
//AbstractInterpreter定義了基於彙編模型的解釋器以及解釋生成器的抽象行爲
//hotspot/src/share/vm/interpreter/AbstractInterpreter.hpp
//hotspot/src/cpu/x86/vm/templateInterpreter_x86_64.cpp
class AbstractInterpreter: AllStatic {
public:
//根據不同的方法特性,給方法定義不同的類型
//爲啥需要區分這麼多種MethodKind了?主要是爲了對特殊的MethodKind的方法做特殊處理,以獲取最大的執行性能力
enum MethodKind {
zerolocals, // method needs locals initialization
zerolocals_synchronized,// method needs locals initialization & is synchronized
native,// native method
native_synchronized,// native method & is synchronized
empty,// empty method (code: _return)
accessor,// accessor method (code: _aload_0, _getfield, _(a|i)return)
abstract,// abstract method (throws an AbstractMethodException)
//枚舉在C/C++中其實就是一個int常量,默認情況下枚舉值從0開始,依次往下遞增,上述method_handle_invoke_LAST的定義比較特殊,正常method_handle_invoke_LAST應該比method_handle_invoke_FIRST大1,上述定義下就大vmIntrinsics::LAST_MH_SIG_POLY - vmIntrinsics::FIRST_MH_SIG_POLY,即method_handle_invoke_FIRST和method_handle_invoke_LAST之間實際還有幾個未定義的但是合法的枚舉值。
method_handle_invoke_FIRST,
method_handle_invoke_LAST = (method_handle_invoke_FIRST + (vmIntrinsics::LAST_MH_SIG_POLY- vmIntrinsics::FIRST_MH_SIG_POLY)),
//....
//枚舉在C/C++中其實就是一個int常量,默認情況下枚舉值從0開始,依次往下遞增,相當於枚舉長度
number_of_method_entries
};
//_entry_table是AbstractInterpreter定義的一個protected address數組,數組長度是MethodKind的枚舉值number_of_method_entries,即每個MethodKind都會有一個對應的表示方法執行入口地址的address
static address _entry_table[number_of_method_entries];
//根據方法類型,從_entry_table中取出對應的方法處理
static address entry_for_kind(MethodKind k){
//校驗MethodKind是否合法,MethodKind是一個枚舉,這裏是檢查枚舉值
assert(0 <= k && k < number_of_method_entries, "illegal kind");
return _entry_table[k];
}
static address entry_for_method(methodHandle m) {
return entry_for_kind(method_kind(m));
}
//根據方法,確定方法類型
AbstractInterpreter::MethodKind AbstractInterpreter::method_kind(methodHandle m) {
//如果是抽象方法,直接返回abstract
if (m->is_abstract()) return abstract;
//如果是MethodHandle內部的私有方法
if (m->is_method_handle_intrinsic()) {
//vmIntrinsics::ID就是用來給部分特殊方法打標的
vmIntrinsics::ID id = m->intrinsic_id();
//校驗ID是否合法,ID也是一個枚舉,判斷是否在枚舉值範圍內
assert(MethodHandles::is_signature_polymorphic(id), "must match an intrinsic");
//根據ID計算MethodKind枚舉值
MethodKind kind = (MethodKind)( method_handle_invoke_FIRST +
((int)id - vmIntrinsics::FIRST_MH_SIG_POLY) );
//校驗計算的MethodKind枚舉值是否合法
assert(kind <= method_handle_invoke_LAST, "parallel enum ranges");
return kind;
}
//.....
// Native method?
// Note: This test must come _before_ the test for intrinsic
// methods. See also comments below.
//如果是本地方法
if (m->is_native()) {
//不能是MethodHandle內部的私有方法
assert(!m->is_method_handle_intrinsic(), "overlapping bits here, watch out");
//根據本地方式是否是同步和非同步方
return m->is_synchronized() ? native_synchronized : native;
}
// 如果是同步方法
if (m->is_synchronized()) {
return zerolocals_synchronized;
}
//如果是構造方法,且要求在初始化時註冊finalizer方法
if (RegisterFinalizersAtInit && m->code_size() == 1 &&
m->intrinsic_id() == vmIntrinsics::_Object_init) {
// We need to execute the special return bytecode to check for
// finalizer registration so create a normal frame.
return zerolocals;
}
// 如果是空方法
if (m->is_empty_method()) {
return empty;
}
//如果是特殊的函數計算方法
switch (m->intrinsic_id()) {
case vmIntrinsics::_dsin : return java_lang_math_sin ;
case vmIntrinsics::_dcos : return java_lang_math_cos ;
case vmIntrinsics::_dtan : return java_lang_math_tan ;
case vmIntrinsics::_dabs : return java_lang_math_abs ;
case vmIntrinsics::_dsqrt : return java_lang_math_sqrt ;
case vmIntrinsics::_dlog : return java_lang_math_log ;
case vmIntrinsics::_dlog10: return java_lang_math_log10;
case vmIntrinsics::_dpow : return java_lang_math_pow ;
case vmIntrinsics::_dexp : return java_lang_math_exp ;
case vmIntrinsics::_Reference_get:
return java_lang_ref_reference_get;
}
//獲取屬性的方法,如this.a,a是屬性,對應的字節碼指令就是getfield
if (m->is_accessor()) {
assert(m->size_of_parameters() == 1, "fast code for accessors assumes parameter size = 1");
return accessor;
}
//其他的非本地非空非初始化的普通方法
return zerolocals;
}
}
//模板解釋器生成器
//hotspot/src/cpu/x86/vm/TemplateInterpreterGenerator.cpp
class TemplateInterpreterGenerator{
void TemplateInterpreterGenerator::generate_all() {
//...
#define method_entry(kind) \
{ CodeletMark cm(_masm, "method entry point (kind = " #kind ")"); \
Interpreter::_entry_table[Interpreter::kind] = generate_method_entry(Interpreter::kind); \
}
// all non-native method kinds
method_entry(zerolocals)
method_entry(zerolocals_synchronized)
method_entry(empty)
method_entry(accessor)
method_entry(abstract)
method_entry(java_lang_math_sin )
method_entry(java_lang_math_cos )
method_entry(java_lang_math_tan )
method_entry(java_lang_math_abs )
method_entry(java_lang_math_sqrt )
method_entry(java_lang_math_log )
method_entry(java_lang_math_log10)
method_entry(java_lang_math_exp )
method_entry(java_lang_math_pow )
method_entry(java_lang_ref_reference_get)
//...
set_entry_points_for_all_bytes();
}
//給所有的字節碼生成對應的彙編指令
void TemplateInterpreterGenerator::set_entry_points_for_all_bytes() {
//逐一遍歷所有的字節碼
for (int i = 0; i < DispatchTable::length; i++) {
Bytecodes::Code code = (Bytecodes::Code)i;
//如果定義了這個字節碼
if (Bytecodes::is_defined(code)) {
//生成對應字節碼的彙編指令
set_entry_points(code);
} else {
//將其標記成未實現
set_unimplemented(i);
}
}
}
void TemplateInterpreterGenerator::set_entry_points(Bytecodes::Code code) {
CodeletMark cm(_masm, Bytecodes::name(code), code);
// 初始化 entry points
assert(_unimplemented_bytecode != NULL, "should have been generated before");
assert(_illegal_bytecode_sequence != NULL, "should have been generated before");
address bep = _illegal_bytecode_sequence;
address zep = _illegal_bytecode_sequence;
address cep = _illegal_bytecode_sequence;
address sep = _illegal_bytecode_sequence;
address aep = _illegal_bytecode_sequence;
address iep = _illegal_bytecode_sequence;
address lep = _illegal_bytecode_sequence;
address fep = _illegal_bytecode_sequence;
address dep = _illegal_bytecode_sequence;
address vep = _unimplemented_bytecode;
address wep = _unimplemented_bytecode;
//如果定義了字節碼
if (Bytecodes::is_defined(code)) {
//獲取該字節碼對應的Template
Template* t = TemplateTable::template_for(code);
assert(t->is_valid(), "just checking");
//生成彙編代碼,最終調用Template::generate方法生成
set_short_entry_points(t, bep, cep, sep, aep, iep, lep, fep, dep, vep);
}
//如果是寬字節
if (Bytecodes::wide_is_defined(code)) {
//獲取該字節碼對應的Template
Template* t = TemplateTable::template_for_wide(code);
assert(t->is_valid(), "just checking");
//生成彙編代碼,最終調用Template::generate方法生成
set_wide_entry_point(t, wep);
}
// 將生成的 entry points放入_normal_table中
EntryPoint entry(bep, zep, cep, sep, aep, iep, lep, fep, dep, vep);
Interpreter::_normal_table.set_entry(code, entry);
Interpreter::_wentry_point[code] = wep;
}
void TemplateInterpreterGenerator::set_unimplemented(int i) {
address e = _unimplemented_bytecode;
EntryPoint entry(e, e, e, e, e, e, e, e, e, e);
Interpreter::_normal_table.set_entry(i, entry);
Interpreter::_wentry_point[i] = _unimplemented_bytecode;
}
//大部分MethodKind對應的address都是通過generate_method_entry方法生成
//method_handle_invoke_FIRST到method_handle_invoke_LAST之間的幾個,他們是通過AbstractInterpreterGenerator::initialize_method_handle_entries完成初始化的
//hotspot/src/cpu/x86/vm/TemplateInterpreter_x86_64.cpp
address AbstractInterpreterGenerator::generate_method_entry(
AbstractInterpreter::MethodKind kind) {
// determine code generation flags
bool synchronized = false;
address entry_point = NULL;
InterpreterGenerator* ig_this = (InterpreterGenerator*)this;
switch (kind) {
case Interpreter::zerolocals : break;
case Interpreter::zerolocals_synchronized: synchronized = true; break;
case Interpreter::native : entry_point = ig_this->generate_native_entry(false); break;
case Interpreter::native_synchronized : entry_point = ig_this->generate_native_entry(true); break;
case Interpreter::empty : entry_point = ig_this->generate_empty_entry(); break;
case Interpreter::accessor : entry_point = ig_this->generate_accessor_entry(); break;
case Interpreter::abstract : entry_point = ig_this->generate_abstract_entry(); break;
//....
default:
fatal(err_msg("unexpected method kind: %d", kind));
break;
}
if (entry_point) {
return entry_point;
}
return ig_this->generate_normal_entry(synchronized);
}
}
//TosState枚舉用來表示字節碼指令執行前後棧頂的值的類型,棧頂的值可能保存在一個或者多個CPU寄存器中,需要通過值類型正確的讀取值,將棧頂的值保存到一個或者多個寄存器中的技術就稱爲棧頂緩存技術,默認情況下棧頂的值保存在rax寄存器中。如果TosState爲vtos則表示未使用棧頂緩存。
//hotspot/src/share/vm/utilities/globalDefinitions.hpp
enum TosState { // describes the tos cache contents
btos = 0, // byte, bool tos cached
ztos = 1, // byte, bool tos cached
ctos = 2, // char tos cached
stos = 3, // short tos cached
itos = 4, // int tos cached
ltos = 5, // long tos cached
ftos = 6, // float tos cached
dtos = 7, // double tos cached
atos = 8, // object cached
vtos = 9, // tos not cached
number_of_states,
ilgl // illegal state: should not occur
};
//hotspot/src/share/vm/interpreter/templateInterpreter.hpp
//棧頂緩存技術,TosState不同,所保存的地方可能不一樣
//用一個數組把地址做保存下來,然後運行時根據取不同的值
class EntryPoint VALUE_OBJ_CLASS_SPEC {
private:
address _entry[number_of_states];
public:
// Construction
EntryPoint();
EntryPoint(address bentry, address zentry, address centry, address sentry, address aentry, address ientry, address lentry, address fentry, address dentry, address ventry);
address entry(TosState state) const;// return target address for a given tosca state
void set_entry(TosState state, address entry);// set target address for a given tosca state
};
//hotspot/src/share/vm/interpreter/templateInterpreter.hpp
//EntryPoint的數組形式
class DispatchTable VALUE_OBJ_CLASS_SPEC {
public:
enum { length = 1 << BitsPerByte };//默認爲8
private:
address _table[number_of_states][length];// dispatch tables, indexed by tosca and bytecode
public:
// Attributes
EntryPoint entry(int i) const; // return entry point for a given bytecode i
void set_entry(int i, EntryPoint& entry); // set entry point for a given bytecode i
address* table_for(TosState state) { return _table[state]; }
address* table_for() { return table_for((TosState)0); }
int distance_from(address *table) { return table - table_for(); }
int distance_from(TosState state) { return distance_from(table_for(state)); }
};
//用來描述一個字節碼模板的屬性,並提供一個用來生成字節碼模板的函數
//根據不同的指令配置不同的參數,生成不同的彙編代碼
//hotspot/src/share/vm/interpreter/templateTable.hpp
class Template VALUE_OBJ_CLASS_SPEC {
typedef void (*generator)(int arg);
//用來描述字節碼模板的屬性,相關屬性通過枚舉Flags指定
int _flags;
//執行字節碼指令前的棧頂值類型
TosState _tos_in;
//執行字節碼指令後的棧頂值類型
TosState _tos_out;
//generator是用來生成字節碼模板的函數的別名
generator _gen;
//用來生成字節碼模板的參數
int _arg;
};
//Template的管理工具
//hotspot/src/share/vm/interpreter/templateTable.hpp
class TemplateTable: AllStatic {
//是否完成初始化
static bool _is_initialized;
//表示各字節碼指令對應的Template
static Template _template_table [Bytecodes::number_of_codes];
//寬字節下的各字節碼指令對應的Template
static Template _template_table_wide[Bytecodes::number_of_codes];
//當前正在生成的Template模板
static Template* _desc;
//用來生成字節碼模板的Assembler實例
static InterpreterMacroAssembler* _masm;
}
//hotspot/src/share/vm/interpreter/templateInterpreter.hpp
//AbstractInterpreter基礎上增加了很多的完成特定功能的函數的調用入口
class TemplateInterpreter: public AbstractInterpreter {
// the active dispatch table (used by the interpreter for dispatch)
static DispatchTable _active_table;
// the normal dispatch table (used to set the active table in normal mode)
static DispatchTable _normal_table;
void TemplateInterpreter::initialize() {
if (_code != NULL) return;
//校驗字節碼的個數必須小於等於DispatchTable的長度
assert((int)Bytecodes::number_of_codes <= (int)DispatchTable::length,
"dispatch table too small");
//初始化一些統計數據
AbstractInterpreter::initialize();
//把所用指令創建Template,並放入數組中
TemplateTable::initialize();
// generate interpreter
{ ResourceMark rm;
TraceTime timer("Interpreter generation", TraceStartupTime);
//InterpreterCodeSize是在平臺相關的templateInterpreter_x86.hpp中定義的,64位下是256 * 1024
int code_size = InterpreterCodeSize;
NOT_PRODUCT(code_size *= 4;) // debug uses extra interpreter code space
//創建一個StubQueue
_code = new StubQueue(new InterpreterCodeletInterface, code_size, NULL,
"Interpreter");
//初始化InterpreterGenerator,初始化的時候會生成所有的調用函數
InterpreterGenerator g(_code);
if (PrintInterpreter) print();
}
// 將_normal_table標記爲激活的
_active_table = _normal_table;
}
}
//InterpreterCodelet表示一段解釋器代碼,所有的解釋器代碼都放在InterpreterCodelet中,同時還包含了額外的用於打印和調試的信息
//hotspot/src/share/vm/interpreter/interpreter.hpp
// ________
// stub -->| | <--+
// | data | |
// |________| |
// code_begin -->| | |
// | | |
// | code | | size
// | | |
// |________| |
// code_end -->| | |
// | data | |
// |________| |
// <--+
class InterpreterCodelet: public Stub {
//內存大小
int _size;
//當前InterpreterCodelet的描述字符串
const char* _description;
//具體字節碼指令的枚舉
Bytecodes::Code _bytecode;
}
//StubQueue表示一個用來保存Stub的隊列
class StubQueue: public CHeapObj<mtCode> {
friend class VMStructs;
private:
//InterpreterCodelet的代理工具類
//_stub_interface+_stub_buffer,得出InterpreterCodelet
StubInterface* _stub_interface;
address _stub_buffer;
//存儲Stub的buffer的內存大小
int _buffer_size;
//存儲Stub的buffer的limit的位置,能夠分配Stub的上限
int _buffer_limit;
//隊列中第一個Stub相對於_stub_buffer的偏移量
int _queue_begin;
//隊列後第一個Stub相對於_stub_buffer的偏移量,即下一個可分配Stub的地址
int _queue_end;
//隊列中保存的stub的個數
int _number_of_stubs;
}
//CodeletMark實例化的時候會向StubQueue分配stub
//創建宏彙編解釋器masm,這樣子通過masm生成的代碼就在stub裏面了
class CodeletMark(
InterpreterMacroAssembler*& masm,
const char* description,
Bytecodes::Code bytecode = Bytecodes::_illegal):
//code()方法返回的就是AbstractInterpreter的StubQueue實例,即通過StubQueue請求指定大小的Stub
_clet((InterpreterCodelet*)AbstractInterpreter::code()->request(codelet_size())),
//通過_clet的兩個屬性,調用CodeBuffer(address code_start, csize_t code_size)方法初始化CodeBuffer
_cb(_clet->code_begin(), _clet->code_size())
{ //校驗_clet非空,即Stub分配成功
assert (_clet != NULL, "we checked not enough space already");
// 初始化InterpreterCodelet
_clet->initialize(description, bytecode);
// 創建一個新的InterpreterMacroAssembler實例,new返回的是masm指針,&masm獲取就是InterpreterMacroAssembler的指針的指針
masm = new InterpreterMacroAssembler(&_cb);
_masm = &masm;
}
~CodeletMark() {
//填充對齊,避免打印時末尾顯示亂碼
(*_masm)->align(wordSize);
//刷新指令緩存
(*_masm)->flush();
// 提交Stub,因爲request是已經獲取全局鎖了,所以不存起其他線程同時修改StubQueue實例
AbstractInterpreter::code()->commit((*_masm)->code()->pure_insts_size(), (*_masm)->code()->strings());
//確保沒有其他人可以在CodeletMark的生命週期外使用_masm,然後InterpreterMacroAssembler會通過ResoureMark自動銷燬掉
*_masm = NULL;
}
解釋如何工作
《JVM方法調用》中提到的entry_point具體怎麼生成的?
- 系統初始化_entry_table,生成不同方法MethodKind調用對應不同的處理方式地址
Threads::create_vm()
init_globals()
interpreter_init()()
emplateInterpreter::initialize()
TemplateInterpreterGenerator() // 構造函數
TemplateInterpreterGenerator::generate_all()
- 在方法調用的時候,根據當前方法求出方法類型MethodKind
AbstractInterpreter::method_kind(methodHandle m)
- 根據方法的MethodKind,在_entry_table查找到對應的地址
address entry_for_kind(MethodKind k)
- 根據地址entry_point進行調用,普通方法對應 generate_normal_entry
方法中各指令是如何執行的
- 爲各指令創建模板Template,並被TemplateTable管理
Threads::create_vm()
init_globals()
interpreter_init()()
TemplateInterpreter::initialize()
TemplateTable::initialize
得template
def(Bytecodes::iload_1 , |||, vtos, itos,iload , 1 );
def(Bytecodes::iconst_1 , |||, vtos, itos, iconst , 1 );
def(Bytecodes::iadd , |||___, itos, itos, iop2 , add );
- 爲所有template生成彙編碼,並存放StubQueue
Threads::create_vm()
init_globals()
interpreter_init()()
TemplateInterpreter::initialize()
TemplateInterpreterGenerator::generate_all()
TemplateInterpreterGenerator::set_entry_points_for_all_bytes
TemplateInterpreterGenerator::set_entry_points
通過CodeletMark向StubQueue申請空間,也創建出_masm彙編器
CodeletMark cm(_masm, Bytecodes::name(code), code);
最終調用Template::generate方法生成彙編代碼
iload_1 27 iload_1 [0x00000001122d4600, 0x00000001122d4660] 96 bytes
iconst_1 4 iconst_1 [0x00000001122d3740, 0x00000001122d37a0] 96 bytes
iadd 96 iadd [0x00000001122d6720, 0x00000001122d6760] 64 bytes
彙編代碼的地址入到轉發表DispatchTable中
Interpreter::_normal_table.set_entry(code, entry);
3.在《JVM方法調用》方法調用的時候調用
void InterpreterMacroAssembler::dispatch_next(TosState state, int step) {
// load next bytecode (load before advancing r13 to prevent AGI)
//r13保存的就是方法的字節碼的內存位置,step表示跳過的字節數,第一次調用時沒有傳step,step默認爲0
//讀取第一個字節碼到rbx中
load_unsigned_byte(rbx, Address(r13, step));
//將r13中的地址自增step
increment(r13, step);
dispatch_base(state, Interpreter::dispatch_table(state));
}
void InterpreterMacroAssembler::dispatch_base(TosState state,
address* table,
bool verifyoop) {
//64位下是空實現
verify_FPU(1, state);
//校驗棧幀size
if (VerifyActivationFrameSize) {
Label L;
mov(rcx, rbp);
subptr(rcx, rsp);
int32_t min_frame_size =
(frame::link_offset - frame::interpreter_frame_initial_sp_offset) *
wordSize;
cmpptr(rcx, (int32_t)min_frame_size);
jcc(Assembler::greaterEqual, L);
stop("broken stack frame");
bind(L);
}
//如果棧頂緩存是對象,則校驗對象
if (verifyoop) {
verify_oop(rax, state);
}
//將table的地址拷貝到rscratch1寄存器中,這時的table地址實際上是DispatchTable的二維數組_table屬性的第一維數組的地址
lea(rscratch1, ExternalAddress((address)table));
//此時rbx是待執行的字節碼,即跳轉到對應字節碼的彙編指令上,DispatchTable的二維數組_table屬性的第二維就是字節碼,即在rscratch1地址上偏移rbx*8個字節就可以找到對應的字節碼指令實現了
jmp(Address(rscratch1, rbx, Address::times_8));
}
JIT
即時編譯器並不是虛擬機必須的部分,Java虛擬機規範並沒有規定Java虛擬機內必須要有即時編譯器存在,更沒有限定或指導即時編譯器應該如何去實現。但是,即時編譯器編譯性能的好壞、代碼優化程度的高低卻是衡量一款商用虛擬機優秀與否的最關鍵的指標之一,它也是虛擬機中最核心且最能體現虛擬機技術水平的部分。
client模式:可通過-client指定,使用c1編譯器,c1對編譯進行快速的優化
sever模式:可通過-sever指定,使用c2編譯器,c2對編譯進行更多優化,編譯比c1耗時,但產生的機器代碼比c1更高效
HotSpot虛擬機中常見的即時編譯器c1,c2.c1能做一些快速的優化,c2優化慢,但產生更高效的代碼。因此 jdk6開始加入了多級編譯器的概念,解釋器,c1,c2編譯器一起協同運行。
- CompLevel_none,採用解釋器執行
- CompLevel_simple,採用c1編翻譯器
- CompLevel_limited_profile,採用c1編翻譯器,採集[方法的方法調用計數,循環調用計數]
- CompLevel_full_profile,採用c1編翻譯器,採集[方法的方法調用計數,循環調用計數,profile信息]
- CompLevel_optimization,採用c2編翻譯器
其中0,2,3三個級別下都會週期性的通知 AdvancedThresholdPolicy某個方法的方法調用計數即invocation counters and循環調用計數即backedge counters,不同級別下通知的頻率不同。這些通知用來決定如何調整編譯級別。
某個方法剛開始執行時是通過解釋器解釋執行的,即level 0,然後AdvancedThresholdPolicy會綜合如下兩個因素和調用計數將編譯級別調整爲2或者3:
C2編譯任務隊列的長度決定了下一個編譯級別。
據觀察,level 2級別下的編譯比level3級別下的編譯快30%,因此我們應該在只有已經收集了充分的profile信息後才採用level 3編譯,從而儘可能縮短level3級別下編譯的耗時。
- 當C2的編譯任務隊列太長的時候,如果選擇level3則編譯會被卡住直到C2將之前的編譯任務處理完成,這時如果選擇Level2編譯則很快完成編譯。
- 當C2的編譯壓力逐步減小,就可以重新在level 3下編譯並且開始收集profile信息。
根據C1編譯任務隊列的長度用來動態的調整閾值.在編譯器過載時,會將已經編譯完成但是不在使用的方法從編譯隊列中移除。
當level 3下profile信息收集完成後就會轉換成level 4了,可根據C2編譯任務隊列的長度來動態調整轉換的閾值。(l3->l4)
當經過C1編譯完成後,基於方法的代碼塊的數量,循環的數量等信息可以判斷一個方法是否是瑣碎(trivial)的,這類方法在C2編譯下會產生和C1編譯一樣的代碼,因此這時會用level1的編譯代替level 4的編譯。
當C1和C2的編譯任務隊列的長度足夠短,也可在level 0下開啓profile信息收集。編譯隊列通過優先級隊列實現,每個添加到編譯任務隊列的方法都會週期的計算其在單位時間內增加的調用次數,每次從編譯隊列中獲取任務時,都選擇單位時間內調用次數最大的一個。基於此,我們也可以將那些編譯完成後不再使用,調用次數不再增加的方法從編譯任務隊列中移除。
常見的各級別轉換路徑如下:
- 0 -> 3 -> 4,最常見的轉換路徑,需要注意這種情況下profile可以從level 0開始,level3結束。
- 0 -> 2 -> 3 -> 4,當C2的編譯壓力較大會發生這種情形,本來應該從0直接轉換成3,爲了減少編譯耗時就從0轉換成2,等C2的編譯壓力降低再轉換成3
- 0 -> (3->2) -> 4,這種情形下已經把方法加入到3的編譯隊列中了,但是C1隊列太長了導致在0的時候就開啓了profile,然後就調整了編譯策略按level 2來編譯,即時還是3的隊列中,這樣編譯更快
- 0 -> 3 -> 1 or 0 -> 2 ->1,當一個方法被C1編譯後,被標記成trivial的,因爲這類方法C2編譯的代碼和C1編譯的代碼是一樣的,所以不使用4,改成1
- 0 ->4,一個方法C1編譯失敗且一直在收集profile信息,就從0切換到4
//java虛擬機參數解析類
//hotspot/src/share/vm/runtime/arguments.cpp
class Arguments {
void Arguments::set_mode_flags(Mode mode) {
//設置編譯參數默認值
set_java_compiler(false);
_mode = mode;
//....
UseInterpreter = true;
UseCompiler = true;
UseLoopCounter = true;
switch (mode) {
default:
ShouldNotReachHere();
break;
//解釋模式:可通過-Xint指定
case _int:
UseCompiler = false;
UseLoopCounter = false;
AlwaysCompileLoopMethods = false;
UseOnStackReplacement = false;
break;
//混合模式,默認格式
case _mixed:/
// same as default
break;
//編譯模式:可通過-Xcomp指定
case _comp:
UseInterpreter = false;
BackgroundCompilation = false;
ClipInlining = false;
if (TieredCompilation) {//多級編譯器
Tier3InvokeNotifyFreqLog = 0;
Tier4InvocationThreshold = 0;
}
break;
}
}
//表示一個調用計數器,可以在初始化時爲不同狀態下的InvocationCounter定義對應的閾值和觸發的動作,當計數超過閾值時自動執行初始化時指定的觸發動作
//hotspot/src/share/vm/interpreter/invocationCounter.hpp
class InvocationCounter{
//爲了節省內存空間,InvocationCounter的狀態和計數被編碼在一個int數據(4字節,32位,對應_counter屬性)
// bit no: |31 3| 2 | 1 0 |
// format: [count|carry|state]
//count具體的計數器
//carry相當於一個分隔符
//state
//wait_for_nothing, do nothing when count() > limit()
//wait_for_compile,introduce nmethod when count() > limit()
unsigned int _counter;
//執行方法編譯的閾值
static int InterpreterInvocationLimit;
//執行棧上替換的閾值
static int InterpreterBackwardBranchLimit;
//收集解釋器執行性能數據的閾值
static int InterpreterProfileLimit;
//不同State下的閾值,到達對應數值,狀態開始變化
static int _init [number_of_states];
//不同State下的達到閾值,狀態開始變化,執行的動作
static Action _action[number_of_states];
void InvocationCounter::init() {
_counter = 0; //所有的位都初始化成0
reset();
}
void InvocationCounter::reset() {
set_state(wait_for_compile);
}
void InvocationCounter::set_state(State state) {
//校驗state的合法性
assert(0 <= state && state < number_of_states, "illegal state");
//獲取該state下的調用計數,初始爲0
int init = _init[state];
// prevent from going to zero, to distinguish from never-executed methods
//初始狀態下count()返回0,init也是0
//當運行一段時間發生狀態切換後,count()返回值大於0,如果此時init==0說明是第一次執行此狀態下的調用,將init初始化爲1
if (init == 0 && count() > 0) init = 1;
int carry = (_counter & carry_mask);
//初始化counter
_counter = (init << number_of_noncount_bits) | carry | state;
}
//返回總的調用計數
int count() const { return _counter >> number_of_noncount_bits; }
void InvocationCounter::reinitialize(bool delay_overflow) {
//確保number_of_states小於等於4
guarantee((int)number_of_states <= (int)state_limit, "adjust number_of_state_bits");
//設置兩種狀態下的觸發動作
def(wait_for_nothing, 0, do_nothing);
//如果延遲處理,delay_overflow肯定是true,所以不會走到dummy_invocation_counter_overflow,該方法是空實現
if (delay_overflow) {
def(wait_for_compile, 0, do_decay);
} else {
def(wait_for_compile, 0, dummy_invocation_counter_overflow);
}
//計算InterpreterInvocationLimit等閾值
InterpreterInvocationLimit = CompileThreshold << number_of_noncount_bits;
InterpreterProfileLimit = ((CompileThreshold * InterpreterProfilePercentage) / 100)<< number_of_noncount_bits;
if (ProfileInterpreter) {
InterpreterBackwardBranchLimit = (CompileThreshold * (OnStackReplacePercentage - InterpreterProfilePercentage)) / 100;
} else {
InterpreterBackwardBranchLimit = ((CompileThreshold * OnStackReplacePercentage) / 100) << number_of_noncount_bits;
}
//校驗計算結果的合法性
assert(0 <= InterpreterBackwardBranchLimit,
"OSR threshold should be non-negative");
assert(0 <= InterpreterProfileLimit &&
InterpreterProfileLimit <= InterpreterInvocationLimit,
"profile threshold should be less than the compilation threshold "
"and non-negative");
}
}
// Method中的一個計數屬性
class MethodCounters: public MetaspaceObj {
//方法調用計數器
InvocationCounter _invocation_counter;
//循環計數器
InvocationCounter _backedge_counter;
}
//參數是generate_normal_entry三個地址
void InterpreterGenerator::generate_counter_incr(
Label* overflow,//方法調用次數到達,跳去提交編譯
Label* profile_method,//以集profile數據的地址
Label* profile_method_continue//以集profile後的地址) {
Label done;
//如果啓用分級編譯,server模式下默認啓用
if (TieredCompilation) {
//因爲InvocationCounter的_counter中調用計數部分是前29位,所以增加一次調用計數不是從1開始,而是1<<3即8
int increment = InvocationCounter::count_increment;
//Tier0InvokeNotifyFreqLog默認值是7,count_shift是_counter屬性中非調用計數部分的位數,這裏是3
int mask = ((1 << Tier0InvokeNotifyFreqLog) - 1) << InvocationCounter::count_shift;
Label no_mdo;
//如果開啓性能收集
if (ProfileInterpreter) {
//校驗Method中的_method_data屬性非空,如果爲空則跳轉到no_mdo
__ movptr(rax, Address(rbx, Method::method_data_offset()));
__ testptr(rax, rax);
__ jccb(Assembler::zero, no_mdo);
//獲取MethodData的_invocation_counter屬性的_counter屬性的地址
const Address mdo_invocation_counter(rax, in_bytes(MethodData::invocation_counter_offset()) +
in_bytes(InvocationCounter::counter_offset()));
//此時rcx中的值無意義
__ increment_mask_and_jump(mdo_invocation_counter, increment, mask, rcx, false, Assembler::zero, overflow);
__ jmp(done);
}
__ bind(no_mdo);
//獲取MethodCounters的_invocation_counter屬性的_counter屬性的地址,get_method_counters方法會將MethodCounters的地址放入rax中
const Address invocation_counter(rax,
MethodCounters::invocation_counter_offset() +
InvocationCounter::counter_offset());
//獲取MethodCounters的地址並將其放入rax中
__ get_method_counters(rbx, rax, done);
//增加計數
__ increment_mask_and_jump(invocation_counter, increment, mask, rcx,
false, Assembler::zero, overflow);
__ bind(done);
} else {
//獲取MethodCounters的_backedge_counter屬性的_counter屬性的地址
const Address backedge_counter(rax,
MethodCounters::backedge_counter_offset() +
InvocationCounter::counter_offset());
//獲取MethodCounters的_invocation_counter屬性的_counter屬性的地址
const Address invocation_counter(rax,
MethodCounters::invocation_counter_offset() +
InvocationCounter::counter_offset());
//獲取MethodCounters的地址並將其放入rax中
__ get_method_counters(rbx, rax, done);
//如果開啓性能收集
if (ProfileInterpreter) {
//因爲value爲0,所以這裏啥都不做
__ incrementl(Address(rax,
MethodCounters::interpreter_invocation_counter_offset()));
}
//更新invocation_counter
__ movl(rcx, invocation_counter);
__ incrementl(rcx, InvocationCounter::count_increment);
__ movl(invocation_counter, rcx); // save invocation count
__ movl(rax, backedge_counter); // load backedge counter
//計算出status的位
__ andl(rax, InvocationCounter::count_mask_value); // mask out the status bits
//將rcx中的調用計數同rax中的status做且運算
__ addl(rcx, rax); // add both counters
// profile_method is non-null only for interpreted method so
// profile_method != NULL == !native_call
if (ProfileInterpreter && profile_method != NULL) {
//如果rcx的值小於InterpreterProfileLimit,則跳轉到profile_method_continue
__ cmp32(rcx, ExternalAddress((address)&InvocationCounter::InterpreterProfileLimit));
__ jcc(Assembler::less, *profile_method_continue);
//如果大於,則校驗methodData是否存在,如果不存在則跳轉到profile_method
__ test_method_data_pointer(rax, *profile_method);
}
//比較rcx的值是否超過InterpreterInvocationLimit,如果大於等於則跳轉到overflow
__ cmp32(rcx, ExternalAddress((address)&InvocationCounter::InterpreteoverflowrInvocationLimit));
__ jcc(Assembler::aboveEqual, *overflow);
__ bind(done);
}
}
void InterpreterMacroAssembler::increment_mask_and_jump(Address counter_addr,
int increment, int mask,
Register scratch, bool preloaded,
Condition cond, Label* where) {
//preloaded一般傳false
if (!preloaded) {
//將_counter屬性的值複製到scratch,即rcx中
movl(scratch, counter_addr);
}
//將_counter屬性增加increment
incrementl(scratch, increment);
//將scratch寄存器中的值寫入到_counter屬性
movl(counter_addr, scratch);
//將mask與scratch中的值做且運算
andl(scratch, mask);
if (where != NULL) {
//如果且運算的結果是0,即達到閾值的時候,則跳轉到where,即overflow處
jcc(cond, *where);
}
}
void InterpreterMacroAssembler::get_method_counters(Register method,
Register mcs, Label& skip) {
Label has_counters;
//獲取當前Method的_method_counters屬性
movptr(mcs, Address(method, Method::method_counters_offset()));
//校驗_method_counters屬性是否非空,如果不爲空則跳轉到has_counters
testptr(mcs, mcs);
jcc(Assembler::notZero, has_counters);
//如果爲空,則調用build_method_counters方法創建一個新的MethodCounters
call_VM(noreg, CAST_FROM_FN_PTR(address,
InterpreterRuntime::build_method_counters), method);
//將新的MethodCounters的地址放入mcs中,校驗其是否爲空,如果爲空則跳轉到skip
movptr(mcs, Address(method,Method::method_counters_offset()));
testptr(mcs, mcs);
jcc(Assembler::zero, skip); // No MethodCounters allocated, OutOfMemory
bind(has_counters);
}
void InterpreterGenerator::generate_counter_overflow(Label* do_continue) {
// Asm interpreter on entry
// r14 - locals
// r13 - bcp
// rbx - method
// edx - cpool --- DOES NOT APPEAR TO BE TRUE
// rbp - interpreter frame
// On return (i.e. jump to entry_point) [ back to invocation of interpreter ]
// Everything as it was on entry
// rdx is not restored. Doesn't appear to really be set.
//InterpreterRuntime::frequency_counter_overflow需要兩個參數,第一個參數thread在執行call_VM時傳遞,第二個參數表明
//調用計數超過閾值是否發生在循環分支上,如果否則傳遞NULL,我們傳遞0,即NULL,如果是則傳該循環的跳轉分支地址
//這個方法返回編譯後的方法的入口地址,如果編譯沒有完成則返回NULL
__ movl(c_rarg1, 0);
__ call_VM(noreg,
CAST_FROM_FN_PTR(address,
InterpreterRuntime::frequency_counter_overflow),
c_rarg1);
//恢復rbx中的Method*,method_offset是全局變量
__ movptr(rbx, Address(rbp, method_offset)); // restore Method*
//跳轉到do_continue標籤
__ jmp(*do_continue, relocInfo::none);
}
nmethod* InterpreterRuntime::frequency_counter_overflow(JavaThread* thread, address branch_bcp) {
//非OSR,即非棧上替換方法,永遠返回null,即不會立即執行編譯,而是提交任務給後臺編譯線程編譯
nmethod* nm = frequency_counter_overflow_inner(thread, branch_bcp);
assert(branch_bcp != NULL || nm == NULL, "always returns null for non OSR requests");
if (branch_bcp != NULL && nm != NULL) {
//目標方法是一個需要棧上替換的方法,因爲frequency_counter_overflow_inner返回的nm沒有加載,所以需要再次查找
frame fr = thread->last_frame();
Method* method = fr.interpreter_frame_method();
int bci = method->bci_from(fr.interpreter_frame_bcp());
nm = method->lookup_osr_nmethod_for(bci, CompLevel_none, false);
}
return nm;
}
//branch_bcp表示調用計數超過閾值時循環跳轉的地址
IRT_ENTRY(nmethod*,
InterpreterRuntime::frequency_counter_overflow_inner(JavaThread* thread, address branch_bcp))
// use UnlockFlagSaver to clear and restore the _do_not_unlock_if_synchronized
// flag, in case this method triggers classloading which will call into Java.
UnlockFlagSaver fs(thread);
frame fr = thread->last_frame();
//驗證當前方法是解釋執行方法
assert(fr.is_interpreted_frame(), "must come from interpreter");
//獲取當前解釋執行的方法
methodHandle method(thread, fr.interpreter_frame_method());
//branch_bcp非空則獲取其相對於方法字節碼起始地址code_base的偏移,否則等於InvocationEntryBci,InvocationEntryBci表明這是非棧上替換的方法編譯
const int branch_bci = branch_bcp != NULL ? method->bci_from(branch_bcp) : InvocationEntryBci;
const int bci = branch_bcp != NULL ? method->bci_from(fr.interpreter_frame_bcp()) : InvocationEntryBci;
//校驗是否發生異常
assert(!HAS_PENDING_EXCEPTION, "Should not have any exceptions pending");
//如果要求棧上替換則返回該方法對應的nmethod,否則返回空,然後提交一個方法編譯的任務給後臺編譯線程
nmethod* osr_nm = CompilationPolicy::policy()->event(method, method, branch_bci, bci, CompLevel_none, NULL, thread);
assert(!HAS_PENDING_EXCEPTION, "Event handler should not throw any exceptions");
if (osr_nm != NULL) {
//如果使用偏向鎖,則將當前棧幀持有的所有偏向鎖都釋放調用,因爲這些偏向鎖在棧上替換的時候需要遷移
if (UseBiasedLocking) {
ResourceMark rm;
GrowableArray<Handle>* objects_to_revoke = new GrowableArray<Handle>();
for( BasicObjectLock *kptr = fr.interpreter_frame_monitor_end();
kptr < fr.interpreter_frame_monitor_begin();
kptr = fr.next_monitor_in_interpreter_frame(kptr) ) {
if( kptr->obj() != NULL ) {
objects_to_revoke->append(Handle(THREAD, kptr->obj()));
}
}
BiasedLocking::revoke(objects_to_revoke);
}
}
return osr_nm;
IRT_END
int Method::bci_from(address bcp) const {
return bcp - code_base();
}
nmethod* lookup_osr_nmethod_for(int bci, int level, bool match_level) {
//method_holder方法返回該方法所屬的Klass
return method_holder()->lookup_osr_nmethod(this, bci, level, match_level);
}
nmethod* InstanceKlass::lookup_osr_nmethod(const Method* m, int bci, int comp_level, bool match_level) const {
// This is a short non-blocking critical region, so the no safepoint check is ok.
//獲取操作OsrList的鎖
OsrList_lock->lock_without_safepoint_check();
//返回_osr_nmethods_head屬性,即棧上替換的nmethod鏈表的頭
nmethod* osr = osr_nmethods_head();
nmethod* best = NULL;
while (osr != NULL) {
//校驗這個方法是棧上替換方法
assert(osr->is_osr_method(), "wrong kind of nmethod found in chain");
if (osr->method() == m &&
(bci == InvocationEntryBci || osr->osr_entry_bci() == bci)) {
//如果要求comp_level匹配
if (match_level) {
//校驗osr的comp_level與待查找方法的comp_level是否匹配
if (osr->comp_level() == comp_level) {
// Found a match - return it.
OsrList_lock->unlock();
return osr;
}
} else {
//查找該方法編譯優化級別最高的osr,如果找到了則返回
if (best == NULL || (osr->comp_level() > best->comp_level())) {
if (osr->comp_level() == CompLevel_highest_tier) {
// Found the best possible - return it.
OsrList_lock->unlock();
return osr;
}
best = osr;
}
}
}
//不是目標方法,繼續查找下一個
osr = osr->osr_link();
}
OsrList_lock->unlock();
//如果沒有最高優化級別的osr,則要求其優化級別大於或者等於要求的級別
if (best != NULL && best->comp_level() >= comp_level && match_level == false) {
return best;
}
return NULL;
}
//編譯好的字節會調用方法進行安裝
void Method::set_code(methodHandle mh, nmethod *code) {
//獲取鎖Patching_lock
MutexLockerEx pl(Patching_lock, Mutex::_no_safepoint_check_flag);
//校驗code非空
assert( code, "use clear_code to remove code" );
//校驗method原來的code爲空或者不是棧上替換的nmethod
assert( mh->check_code(), "" );
//校驗adaper不爲空
guarantee(mh->adapter() != NULL, "Adapter blob must already exist!");
//下面的屬性修改必須按照順序依次執行
//將目標方法的_code屬性置爲code
mh->_code = code; // Assign before allowing compiled code to exec
//獲取編譯級別
int comp_level = code->comp_level();
//如果大於該方法曾經的最高編譯級別則更新
if (comp_level > mh->highest_comp_level()) {
mh->set_highest_comp_level(comp_level);
}
//讓上述修改立即生效,內存屏障相關
OrderAccess::storestore();
#ifdef SHARK
//如果採用Shark編譯器,則直接將方法調用入口地址改寫成insts_begin
mh->_from_interpreted_entry = code->insts_begin();
#else //如果採用非Shark即使用C1或者C2編譯器
//將_from_compiled_entry賦值成verified_entry_point
mh->_from_compiled_entry = code->verified_entry_point();
OrderAccess::storestore();
//如果不是MethodHandle的invoke等方法,即編譯代碼可以立即執行
if (!mh->is_method_handle_intrinsic())
//get_i2c_entry方法實際返回的是一個適配器,在_from_interpreted_entry和_from_compiled_entry之間的適配器
mh->_from_interpreted_entry = mh->get_i2c_entry();
#endif //!SHARK
}
address Method::get_i2c_entry() {
assert(_adapter != NULL, "must have");
return _adapter->get_i2c_entry();
}
//表示編譯策略,即決定那個方法應該編譯和用什麼類型的編譯器編譯
//hotspot/src/share/vm/runtime/compilationPolicy.hpp
class CompilationPolicy : public CHeapObj<mtCompiler> {
//單例,運行時使用的CompilationPolicy
static CompilationPolicy* _policy;
//用於統計編譯耗時
static elapsedTimer _accumulated_time;
//是否正在VM啓動中
static bool _in_vm_startup;
//靜態方法都是工具類方法,判斷某個方法能否編譯
//must_be_compiled,can_be_compiled,can_be_osr_compiled
//禁用某個方法的編譯
//disable_compilation
//用來從CompileQueue隊列中選取一個編譯任務CompileTask
//select_task
//COMPILER2表示啓用C2編譯器,TIERED表示啓用分級編譯,這兩個都是x86_64下默認添加的宏
//server模式下默認開啓分層編譯是選擇AdvancedThresholdPolicy編譯策略。
void compilationPolicy_init() {
CompilationPolicy::set_in_vm_startup(DelayCompilationDuringStartup);
switch(CompilationPolicyChoice) {
case 0:
CompilationPolicy::set_policy(new SimpleCompPolicy());
break;
case 1:
#ifdef COMPILER2
CompilationPolicy::set_policy(new StackWalkCompPolicy());
#else
Unimplemented();
#endif
break;
case 2:
#ifdef TIERED
CompilationPolicy::set_policy(new SimpleThresholdPolicy());
#else
Unimplemented();
#endif
break;
case 3:
#ifdef TIERED
CompilationPolicy::set_policy(new AdvancedThresholdPolicy());
#else
Unimplemented();
#endif
break;
default:
fatal("CompilationPolicyChoice must be in the range: [0-3]");
}
CompilationPolicy::policy()->initialize();
}
}
//簡單的策略
//hotspot/src/share/vm/runtime/simpleThresholdPolicy.hpp
class SimpleThresholdPolicy : public CompilationPolicy {
//表示C1和C2編譯線程的數量
int _c1_count, _c2_count;
//event方法用於觸發熱點方法的編譯,如果是osr方法則返回包含編譯代碼的nmethod實例,如果是非ors方法,則返回NULL
nmethod* SimpleThresholdPolicy::event(methodHandle method, methodHandle inlinee,
int branch_bci, int bci, CompLevel comp_level, nmethod* nm, JavaThread* thread) {
//如果目標線程是隻使用解釋模式執行且編譯級別就是解釋執行,則返回NULL
if (comp_level == CompLevel_none &&
JvmtiExport::can_post_interpreter_events() &&
thread->is_interp_only_mode()) {
return NULL;
}
//...
//如果是非棧上替換方法
if (bci == InvocationEntryBci) {
//提交方法編譯任務
method_invocation_event(method, inlinee, comp_level, nm, thread);
} else {
//如果是棧上替換方法,觸發方法編譯
method_back_branch_event(method, inlinee, bci, comp_level, nm, thread);
//查找不低於目標編譯級別的nmethod實例
nmethod* osr_nm = inlinee->lookup_osr_nmethod_for(bci, comp_level, false);
if (osr_nm != NULL && osr_nm->comp_level() > comp_level) {
// Perform OSR with new nmethod
return osr_nm;
}
}
return NULL;
}
void SimpleThresholdPolicy::compile(methodHandle mh, int bci, CompLevel level, JavaThread* thread) {
//校驗編譯級別有效性
assert(level <= TieredStopAtLevel, "Invalid compilation level");
if (level == CompLevel_none) {
return;
}
//如果不能通過C1編譯則繼續收集解釋執行的性能信息,然後通過C2編譯,如果不能通過C2編譯,則只通過C1編譯
if (!can_be_compiled(mh, level)) {
if (level == CompLevel_full_optimization && can_be_compiled(mh, CompLevel_simple)) {
compile(mh, bci, CompLevel_simple, thread);
}
return;
}
//如果是棧上替換,但是無法編譯則返回
if (bci != InvocationEntryBci && mh->is_not_osr_compilable(level)) {
return;
}
//如果該方法不再編譯隊列中
if (!CompileBroker::compilation_is_in_queue(mh)) {
if (PrintTieredEvents) {
//打印日誌
print_event(COMPILE, mh, mh, bci, level);
}
//提交編譯任務
submit_compile(mh, bci, level, thread);
}
}
}
//分層策略
//hotspot/src/share/vm/runtime/advancedThresholdPolicy.hpp
class AdvancedThresholdPolicy : public SimpleThresholdPolicy {
jlong _start_time;//啓動時間
double _increase_threshold_at_ratio;//表示當CodeCache已使用了指定比例時提升C1編譯的閾值
//方法調用
void AdvancedThresholdPolicy::method_invocation_event(methodHandle mh, methodHandle imh,
CompLevel level, nmethod* nm, JavaThread* thread) {
//如果需要開啓profile,則初始化方法的mdo
if (should_create_mdo(mh(), level)) {
create_mdo(mh, thread);
}
//如果開啓方法編譯,且該方法不再編譯隊列中
if (is_compilation_enabled() && !CompileBroker::compilation_is_in_queue(mh)) {
CompLevel next_level = call_event(mh(), level);
if (next_level != level) {
//執行方法編譯
compile(mh, InvocationEntryBci, next_level, thread);
}
}
}
//循環體編譯,棧上替換
void AdvancedThresholdPolicy::method_back_branch_event(methodHandle mh, methodHandle imh,
int bci, CompLevel level, nmethod* nm, JavaThread* thread) {
//..
//執行方法編譯
compile(imh, bci, next_osr_level, thread);
}
//提交編譯
void AdvancedThresholdPolicy::submit_compile(methodHandle mh, int bci, CompLevel level, JavaThread* thread) {
//....
CompileBroker::compile_method(mh, bci, level, mh, hot_count, "tiered", thread);
}
}
//CompilerThread表示一個專門執行後臺編譯的線程
//hospot src/share/vm/runtime/thread.hpp
//以下是其初始化代碼,默認執行compiler_thread_entry
static void compiler_thread_entry(JavaThread* thread, TRAPS) {
//校驗當前線程必須是CompilerThread
assert(thread->is_Compiler_thread(), "must be compiler thread");
//循環的從編譯隊列中獲取編譯任務執行編譯
CompileBroker::compiler_thread_loop();
}
// Create a CompilerThread
CompilerThread::CompilerThread(CompileQueue* queue, CompilerCounters* counters)
//這裏的compiler_thread_entry是一個方法指針,是該線程啓動後自動執行的方法
: JavaThread(&compiler_thread_entry) {
_env = NULL;
_log = NULL;
_task = NULL;
_queue = queue;
_counters = counters;
_buffer_blob = NULL;
_scanned_nmethod = NULL;
_compiler = NULL;
//設置ResourceArea的類型
resource_area()->bias_to(mtCompiler);
#ifndef PRODUCT
_ideal_graph_printer = NULL;
#endif
}
JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
Thread()
{
if (TraceThreadEvents) {
tty->print_cr("creating thread %p", this);
}
//初始化JavaThread的各項屬性
initialize();
//正常的Java線程都是_not_attaching_via_jni,只有本地線程纔是_attached_via_jni
_jni_attach_state = _not_attaching_via_jni;
//設置線程的調用方法
set_entry_point(entry_point);
//判斷ThreadType
os::ThreadType thr_type = os::java_thread;
thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
//創建一個新的線程 os::java_thread;
os::create_thread(this, thr_type, stack_sz);
}
//hotspot src/share/vm/code/nmethod.hpp
//用於表示一個編譯後的Java方法
class nmethod : public CodeBlob {
//其初始化是從代碼緩存申請空間
void* nmethod::operator new(size_t size, int nmethod_size) throw() {
return CodeCache::allocate(nmethod_size);
}
}
//表示編譯隊列中的一個編譯任務,用來裝載編譯請求
//hospot src/share/vm/compiler/compileBroker.hpp
class CompileTask : public CHeapObj<mtCompiler> {
void CompileTask::set_code(nmethod* nm) {
if (_code_handle == NULL && nm == NULL) return;
//校驗_code_handle不爲空
guarantee(_code_handle != NULL, "");
//最終回調Method:set_code
_code_handle->set_code(nm);
if (nm == NULL) _code_handle = NULL; // drop the handle also
}
}
//表示CompileTask隊列
//hospot src/share/vm/compiler/compileBroker.hpp
class CompileQueue : public CHeapObj<mtCompiler> {
}
//編譯器的基類,定義了獲取編譯器相關屬性的公共方法
//hospot src/share/vm/compiler/abstractCompiler.hpp
class AbstractCompiler : public CHeapObj<mtCompiler> {
//基子類情況
//Compiler就是C1編譯器即client編譯器,
//hospot src/share/vm/c1/c1_Compiler.hpp
//c1目錄下所有類都是該編譯器的實現;
//C2Compiler就是C2編譯器即server編譯器,
//hospot src/share/vm/opto/c2Compiler.hpp中
//opto目錄所有類都是該編譯器的相關實現;
//SharkCompiler就是新的基於LLVM架構的編譯器,
//hospot src/share/vm/shark/sharkCompiler.hpp
//shark目錄下相關類都是該編譯器的相關實現,因爲SharkCompiler目前是基於老舊的LLVM 3.x開發的,很長時間都未更新且存在明顯性能問題,所以OpenJDK計劃將其完全移除。
}
//編譯統一對外類
//hospot src/share/vm/compiler/compileBroker.hpp
class CompileBroker: AllStatic {
void CompileBroker::compilation_init() {
//...
//計算出c1,c2隊列個數
int c1_count = CompilationPolicy::policy()->compiler_count(CompLevel_simple);
int c2_count = CompilationPolicy::policy()->compiler_count(CompLevel_full_optimization);
}
//創建編譯器
if (c1_count > 0) {
_compilers[0] = new Compiler();
}
if (c2_count > 0) {
_compilers[1] = new C2Compiler();
}
//任務隊列,線程初始化
init_compiler_threads(c1_count, c2_count);
//...
}
void CompileBroker::compiler_thread_loop() {
//...
//一直循環
while (!is_compilation_disabled_forever()) {
//....
//從隊列中取任務
CompileTask* task = queue->get();
if (task == NULL) {
continue;
}
//執行任務
//創建nmethod
//最終會反調用Method::set_code
invoke_compiler_on_method(task);
}
}
}
JIT如何運行
使用-Xint解釋器模式關閉JIT
- 通過Arguments類,解析-Xint參數
UseCompiler = false;
- 結合《JVM方法調用》generate_normal_entry方法
inc_counter = false
關閉了對方法計數統計,及超標後去提交統計的可能
使用-Xmixed混合模式的JIT調用過程
- 通過Arguments類,解析-Xmixed參數
UseCompiler = true;
ProfileInterpreter = false;
- 初始化編譯閥值等待參數
Threads::create_vm()
init_globals()
interpreter_init()()
TemplateInterpreter::initialize
InvocationCounter::reinitialize
- 初始化方法中的MethodCounters的計數器
方法調用過程都會觸發Method中MethodCounters計數器初始化
- 調用generate_counter_incr方法,累加MethodCounters計數器
//獲取MethodCounters的_invocation_counter屬性的_counter屬性的地址
const Address invocation_counter(rax,
MethodCounters::invocation_counter_offset() +
InvocationCounter::counter_offset());
__ incrementl(rcx, InvocationCounter::count_increment);
5.判斷閥值,是否要提交編譯
//比較rcx的值是否超過InterpreterInvocationLimit,如果大於等於則跳轉到overflow
__ cmp32(rcx, ExternalAddress((address)&InvocationCounter::InterpreteoverflowrInvocationLimit));
__ jcc(Assembler::aboveEqual, *overflow);
- 代碼跳轉到generate_counter_overflow,向編譯器提交編譯請求,
//如果要求棧上替換則返回該方法對應的nmethod,否則返回空,然後提交一個方法編譯的任務給後臺編譯線程
nmethod* osr_nm = CompilationPolicy::policy()->event(method, method, branch_bci, bci, CompLevel_none, NULL, thread);
- 返回解釋執行此方法
//恢復rbx中的Method*,method_offset是全局變量
__ movptr(rbx, Address(rbp, method_offset)); // restore Method*
//跳轉到do_continue標籤,爲解釋器地址
__ jmp(*do_continue, relocInfo::none);
如何成爲熱點代碼觸發編譯
程序中的代碼只有是熱點代碼時,纔會編譯爲本地代碼,那麼什麼是熱點代碼呢?運行過程中會被即時編譯器編譯的“熱點代碼”有兩類:
1、被多次調用的方法(同上)。
2、被多次執行的循環體(主要goto指令模板中)。當某個方法往回跳轉的次數即循環的次數超過閾值會觸發方法的即時編譯,並且用編譯後的本地代碼替換掉原來的字節碼指令,所謂的棧上替換就是替換調用入口地址,將原來解釋器上的變量,monitor等遷移到編譯後的本地代碼對應的棧幀中《OSR(On-Stack Replacement )》
方法的編譯策略
- 初始化策略及編譯環境(線程,隊列,編譯器)
Threads::create_vm()
init_globals()
compilationPolicy_init()
Threads::create_vm()
CompileBroker::compilation_init
-
方法調用進通過CompilationPolicy::policy()->event()觸發
-
CompilationPolicy通過編譯發法調用次數,及c1,c2隊列度等信息調解編譯參數,並提交
具體調解過程略
CompileBroker::compile_method(mh, bci, level, mh, hot_count, “tiered”, thread);
- 將方法編譯請求封裝成task,加入對列
CompileBroker::compile_method
CompileBroker::compile_method_base
CompileBroker::create_compile_task
CompileQueue::add
- 通過CompilerThread循環從CompileQueue取任務,編譯,反調Method::set_code
編譯方法的執行
1.方法的鏈接過程對Method對象初始化
c2i_entry//從本地代碼執行跳轉到字節碼解釋執行
i2i_entry//字節碼解釋執行的入口地址
_from_compiled_entry = c2i_entry
_from_interpreted_entry = i2i_entry
2.編譯器調用set_code方式安裝編譯代碼
//即其他本地方法調用該方法從c2i_entry變成了直接的起始地址,因爲從本地代碼中調用該方法的本地代碼,不涉及棧幀狀態轉換
_from_compiled_entry = code->verified_entry_point();//編譯代碼的起始地址,
//Java方法調用該方法的入口地址從正常的_i2i_entry變成了adaper的i2c_entry,因爲此時是從字節碼執行切換到本地代碼執行
_from_interpreted_entry = = mh->get_i2c_entry(); // 字節碼執行切換到本地代碼執行
- 《JVM方法調用》方法調用使用
address entry_point = method->from_interpreted_entry();
call entry_point
主要參考
《hotspot實戰》
《Hotspot 字節碼執行與棧頂緩存實現 源碼解析》
《Hotspot 方法調用之TemplateInterpreter 源碼解析》
《Hotspot 方法調用之Interpreter 源碼解析》
《Hotspot 熱點代碼編譯和棧上替換 源碼解析》
《Hotspot 編譯代碼的安裝與卸載 源碼解析》
《Hotspot 方法編譯之CompileTask 源碼解析》
《Hotspot 方法編譯之CompilationPolicy 源碼解析》