線(xiàn)索解釋
一般來(lái)說(shuō)窜锯,當(dāng)提起解釋器的時(shí)候突倍,大部分人腦海里面浮現(xiàn)出來(lái)的模型都是:
while(instruction != null){
switch (instruction) {
case ins1: doSomething1();
case ins2: doSomething2();
...
}
instruction = getNextInstruction();
}
其典型特征就是横漏,在一個(gè)主循環(huán)里面使用switch來(lái)做指令的分派。這種解釋器模式被稱(chēng)為譯碼分派(decode-and-dispatch)苛萎,使用switch的情況下,也被稱(chēng)為“switch-and-dispatch”检号。
這種實(shí)現(xiàn)方式雖然簡(jiǎn)單直接腌歉,但是其本身在性能上表現(xiàn)十分糟糕。這種模式齐苛,最為影響性能的地方就在于指令分派那一步翘盖。指令分派需要線(xiàn)性遍歷每一條指令,最糟糕的情況下凹蜂,就是要遍歷整個(gè)指令集馍驯「笪#控制流的轉(zhuǎn)移也會(huì)對(duì)性能產(chǎn)生很大的影響。在現(xiàn)代處理器上汰瘫,性能提高的一個(gè)很重要因素就是指令預(yù)測(cè)狂打。然而在這種譯碼分派模式下,指令預(yù)測(cè)變得十分困難混弥。
很顯然趴乡,HotSpot并沒(méi)有采用這種模式,它采用了一種被稱(chēng)為線(xiàn)索解釋?zhuān)═hreaded interpretation)的方法蝗拿。該方法對(duì)于譯碼分派有一個(gè)十分重大的改進(jìn):它將跳轉(zhuǎn)地址直接附在了指令之后浙宜。如圖:
這里的跳轉(zhuǎn)地址,在HotSpot使用模板解釋器的情況下蛹磺,實(shí)際上是下一條字節(jié)碼指令對(duì)應(yīng)的機(jī)器碼所在的地址粟瞬。
模板解釋器
模板解釋器是一個(gè)很神奇的東西,它和一般人想的解釋是有很大的區(qū)別的萤捆。按照一般的想法裙品,我們知道HotSpot是利用C++來(lái)實(shí)現(xiàn)的,那么相當(dāng)然的就是以為對(duì)于每一條字節(jié)碼指令來(lái)說(shuō)俗或,其對(duì)應(yīng)的解釋例程就是一段C++代碼市怎。這也是對(duì)的,HotSpot早期的解釋器就是這樣實(shí)現(xiàn)的辛慰,這種解釋器被稱(chēng)為字節(jié)碼解釋器区匠。
但是用C++來(lái)解釋一條字節(jié)碼指令,肯定是很低效的帅腌〕叟可以想見(jiàn)的是,每一條字節(jié)碼的執(zhí)行速客,都需要很長(zhǎng)的一段C++代碼戚篙。舉個(gè)例子,add指令的C++實(shí)現(xiàn)方法溺职,大概是先訪(fǎng)問(wèn)兩次內(nèi)存(也可以訪(fǎng)問(wèn)一次岔擂,而后在分割),將操作數(shù)從操作數(shù)棧取出來(lái)浪耘,而后使用C++的加法操作符將其相加乱灵,然后再將結(jié)果寫(xiě)進(jìn)去操作數(shù)棧。整個(gè)過(guò)程七冲,至少需要訪(fǎng)問(wèn)兩次內(nèi)存痛倚,還需要三個(gè)C++局部變量。
為了進(jìn)一步提高性能癞埠,HotSpot使用了模板解釋器状原。模板解釋器概念上十分簡(jiǎn)單聋呢,就是每一條字節(jié)碼指令都對(duì)應(yīng)一個(gè)機(jī)器碼模板。這部分模板被放置在HotSpot的TemplateTable中:
// src/share/vm/interpreter/templateTable.cpp
void TemplateTable::initialize() {
//其余代碼颠区,一些變量初始化
// 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 );
//其余指令的定義
// 其余代碼
}
機(jī)器碼生成是在前面代碼中的generator那一列被指定的生成器完成的削锰。舉例來(lái)說(shuō),在Bytecodes::_iconst_0的字節(jié)碼模板定義里面毕莱,指定的生成器叫做iconst器贩。因?yàn)闄C(jī)器碼是依賴(lài)于具體的指令集架構(gòu)的,所以這部分代碼放在:
// src/cpu/x86/vm/templateTable_x86_64.cpp
void TemplateTable::iconst(int value) {
transition(vtos, itos);
if (value == 0) {
__ xorl(rax, rax);
} else {
__ movl(rax, value);
}
}
機(jī)器碼生成
前面我們還提到朋截,HotSpot是依賴(lài)于線(xiàn)索解釋的蛹稍,也就是說(shuō),在當(dāng)前字節(jié)碼指令對(duì)應(yīng)的機(jī)器碼指令執(zhí)行完成之后部服,應(yīng)該跳轉(zhuǎn)到下一條字節(jié)碼指令對(duì)應(yīng)的第一條機(jī)器碼指令的地址上唆姐。
要理解這一點(diǎn),要先回到機(jī)器碼生成最開(kāi)始的地方:
// src/share/vm/interpreter/templateInterpreter.cpp
void TemplateInterpreterGenerator::generate_and_dispatch(Template* t, TosState tos_out) {
//其余代碼
// generate template
t->generate(_masm);
// advance
if (t->does_dispatch()) {
//...一些代碼
} else {
// dispatch to next bytecode
__ dispatch_epilog(tos_out, step);
}
}
最關(guān)鍵就是t->generate(_masm)和dispatch_epilog(tos_out, step)廓八。第一句就是生成該字節(jié)碼對(duì)應(yīng)的機(jī)器碼奉芦,參數(shù)_masm就是匯編生成器。
后面一句則是跳轉(zhuǎn)到了下一條字節(jié)碼指令那里剧蹂。事實(shí)上声功,HotSpot第一步的確是找到下一條字節(jié)碼指令,這是通過(guò)將存儲(chǔ)現(xiàn)在字節(jié)碼地址的寄存器的值加上指令長(zhǎng)度來(lái)實(shí)現(xiàn)的宠叼。但是HotSpot在執(zhí)行機(jī)器碼指令的時(shí)候先巴,執(zhí)行完當(dāng)前字節(jié)碼指令的最后一條機(jī)器碼指令之后,跳轉(zhuǎn)的是下一條字節(jié)碼指令對(duì)應(yīng)的第一條機(jī)器碼指令地址冒冬。也就是意味著伸蚯,在生成字節(jié)碼對(duì)應(yīng)的機(jī)器碼之后,還要再生成跳轉(zhuǎn)的機(jī)器碼指令窄驹。
我們可以繼續(xù)追溯這個(gè)dispatch_epilog(tos_out, step)方法朝卒,直到:
// src/cpu/x86/vm/interp_masm_x86_64.cpp
void InterpreterMacroAssembler::dispatch_base(TosState state,
address* table,
bool verifyoop) {
// ...其余代碼
lea(rscratch1, ExternalAddress((address)table));
jmp(Address(rscratch1, rbx, Address::times_8));
}
jmp這一句,就是生成了這條跳轉(zhuǎn)的機(jī)器碼指令乐埠。
關(guān)于模板解釋器的源碼解讀,網(wǎng)上有很多的資源囚企,讀者可以自行去查找丈咐,我這里就不重復(fù)前人已經(jīng)做得很好的工作了