前言

本文简要介绍 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 偏移量处

其它字节码实现都可以用类似的分析方法,这里就不展开,会在专门的文章中进行分析

总结


xingpingz
122 声望64 粉丝

博学,审问,慎思,明辨,力行