解释器是Java虚拟机非常重要的一部分,它的工作就是把字节码转化为机器码并运行。Hotspot中实现了两种具体的解释器,分别是模板解释器而C++解释器。同时在Hotspot的源码树中还保留着一种“字节码解释器”的古老解释器。这种解释器没有编译优化,效率比较低,本质上就是一个比较长的switch语句,针对不同的字节码执行不同的操作。这些操作直接以C/C++语言的形式表现出来,随着项目的编译一起形成了“硬性的”机器代码。相比之下,模板解释器的工作要更为复杂一些,效率也会更高一些。 模板解释器的主要构成:1.解释器,读取字节码,一边翻译一遍执行;2.代码生成器:利用宏汇编器向代码缓存空间写入生成的代码;3.InterpreterCodelet:由解释器运行的代码片段;4.转发表:为了方便找到与字节码对应的机器码。
模板解释器是严重依赖于模板表的。每个指令都会有对应的模板,相似的指令会公用一个模板。模板其实就是一个函数,共用模板就是一个带参数的函数。举一个简单的例子iconst_<n>,这个指令的含义是:将int类型的n推送至栈顶,n=0~5。从这里就可以看出来模板表的优势所在了,五个字节码共用了一个模板,节省了一定的代码量。iconst_<n>的模板是这样定义的:
void TemplateTable::iconst(int value) { transition(vtos, itos); if (value == 0) { __ xorptr(rax, rax); } else { __ movptr(rax, value); } }
看到这里我疑惑了,问题一:这个模板函数如何生成汇编代码?问题二:这个__又是什么呢?这其实是同一个问题。首先看一看产生汇编代码的这个生成函数:
void Template::generate(InterpreterMacroAssembler* masm) { // parameter passing TemplateTable::_desc = this; TemplateTable::_masm = masm; // code generation _gen(_arg); masm->flush(); }
根据注释就可以看出,前两句是传递参数的,后两句是产生代码的。但是_gen(_arg)如何产生代码呢?在我们这个例子中,_gen()就是iconst()函数。而且一个显而易见的问题就是_masm是如何生效的。然后我查找到了这个关键的宏:
//stubGenerator_x86_32.cpp (src\...\vm):58 #define __ _masm-> #define a__ ((Assembler*)_masm)->
这样一来,两个问题就都明白了。__ xorptr(rax,rax)就相当于_masm->xorptr(),这些函数都是_masm汇编器的成员函数。xorptr()最终调用了xorq()函数,其代码如下:
void Assembler::xorq(Register dst, Register src) { (void) prefixq_and_encode(dst->encoding(), src->encoding()); emit_arith(0x33, 0xC0, dst, src); }
随后产生的代码就被放到Code Cache中了,这样一个个的编译模板,最终就会得到所有的代码片段。解释器执行的时候,只需要查转发表就可以实现字节码到机器码的翻译。
发表评论