前言
本文简要介绍 hotspot zero 解释器中 BytecodeInterpreter 类解释执行字节码过程,用到的相关的概念可以参考之前关于 zero 解释器相关的文章
解释器入口函数
zero 解释器是一种用 C++ 语言实现的字节码解释器,参考之前的文章,解释执行的入口在 cppInterpreter_zero.cpp 文件中:
void CppInterpreter::main_loop(int recurse, TRAPS) {
JavaThread *thread = (JavaThread *) THREAD;
ZeroStack *stack = thread->zero_stack();
// If we are entering from a deopt we may need to call
// ourself a few times in order to get to our frame.
if (recurse)
main_loop(recurse - 1, THREAD);
InterpreterFrame *frame = thread->top_zero_frame()->as_interpreter_frame();
interpreterState istate = frame->interpreter_state();
Method* method = istate->method();
intptr_t *result = NULL;
int result_slots = 0;
while (true) {
...
// Call the interpreter
if (JvmtiExport::can_post_interpreter_events())
BytecodeInterpreter::runWithChecks(istate);
else
BytecodeInterpreter::run(istate);
}
}
函数的开始先从 JavaThread 中获取 ZeroStack(堆栈),然后获取之前压入堆栈的 InterpreterFrame(栈帧),栈帧中保存这当前要执行的方法 Method,最后在 while 循环里调用 BytecodeInterpreter::run 方法开始执行字节码
解释器 run 方法
BytecodeInterpreter run 方法比较长,里面充斥着各种 C/C++ 奇技淫巧(宏),大体的脉络还是能够看出来的,我们集中注意力关注核心逻辑:
#if defined(VM_JVMTI)
void
BytecodeInterpreter::runWithChecks(interpreterState istate) {
#else
void
BytecodeInterpreter::run(interpreterState istate) {
#endif
...
#ifndef USELABELS
while (1)
#endif
{
#ifndef PREFETCH_OPCCODE
opcode = *pc;
#endif
opcode_switch:
assert(istate == orig, "Corrupted istate");
#ifdef USELABELS
DISPATCH(opcode);
#else
switch (opcode)
#endif
...
}
VM_JVMTI 宏?
USELABELS 宏,判断是使用 while 循环,还是基于标签的跳转
我们先假定 USELABELS 为 false,得到下面的代码:
while (1) {
switch (opcode) {
...
}
}
这一下子地球上的程序员基本都能理解了,就是一个"死"循环,不断 switch opcode(字节码),jvm 字节码虽然很精简,但是如果一条指令一条指令的 switch case,也会把人烦死,这时候宏定义又派上用场了,我们来看看 switch 开始部分关于常量加载的代码:
switch (opcode)
{
...
#undef OPC_CONST_n
#define OPC_CONST_n(opcode, const_type, value) \
CASE(opcode): \
SET_STACK_ ## const_type(value, 0); \
UPDATE_PC_AND_TOS_AND_CONTINUE(1, 1);
OPC_CONST_n(_iconst_m1, INT, -1);
OPC_CONST_n(_iconst_0, INT, 0);
OPC_CONST_n(_iconst_1, INT, 1);
OPC_CONST_n(_iconst_2, INT, 2);
OPC_CONST_n(_iconst_3, INT, 3);
OPC_CONST_n(_iconst_4, INT, 4);
OPC_CONST_n(_iconst_5, INT, 5);
...
OPC_CONST_n 是一个宏定义,用于生成 _iconst_x 指令动作,将常量加载到 "操作数栈",套用该宏定义,OPC_CONST_n(iconst_0, INT, 0) 会被展开成:
case (_iconst_0):
SET_STACK_INT(0, 0);
UPDATE_PC_AND_TOS_AND_CONTINUE(1, 1);
SET_CONST_INT 宏定义在 bytecodeInterpreter_zero.hpp 文件中(里面还定义着其它宏):
#define SET_STACK_INT(value, offset) (*((jint *)&topOfStack[-(offset)]) = (value))
将 value 存入相对于栈定 offset 偏移量处
其它字节码实现都可以用类似的分析方法,这里就不展开,会在专门的文章中进行分析
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。