JVM 解释器(不是 JIT 编译器)实际上做了什么?
What does JVM interpreter (NOT the JIT compiler) actually do?
请注意,我的问题是关于 JVM 解释器的,而不是 JIT 编译器。 JIT 编译器将 java 字节码转换为本机机器码。因此,这必须意味着 JVM 中的解释器不会将字节码转换为机器码。因此问题来了:解释器本质上是做什么的?如果有人可以用一个相当于 1+1 = 2 的字节码的简单示例来帮助我回答这个问题,即解释器在执行此添加操作时做了什么? (我隐含的问题是,如果解释器没有翻译成 CPU 然后执行 ADD 操作的机器代码,那么这个操作是如何执行的?实际执行什么机器代码来支持这个 ADD 操作?)
表达式1+1
将编译为以下字节码:
iconst_1
iconst_1
add
(实际上,它只会编译为 iconst_2
,因为 Java 编译器执行常量折叠,但为了这个答案的目的,让我们忽略它。)
所以要弄清楚解释器对那些指令究竟做了什么,我们应该分别看一下its source code. The relevant sections for const_1
and add
start at line 983 and line 1221,所以让我们看一下:
#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);
// goes on for several other constants
//...
#define OPC_INT_BINARY(opcname, opname, test) \
CASE(_i##opcname): \
if (test && (STACK_INT(-1) == 0)) { \
VM_JAVA_ERROR(vmSymbols::java_lang_ArithmeticException(), \
"/ by zero", note_div0Check_trap); \
} \
SET_STACK_INT(VMint##opname(STACK_INT(-2), \
STACK_INT(-1)), \
-2); \
UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1); \
// and then the same thing for longs instead of ints
OPC_INT_BINARY(add, Add, 0);
// other operators
整个事情都在检查当前指令的操作码的 switch 语句中。
如果我们扩展宏魔法,用一个极其简化的模板替换周围的代码,并做一些简化的假设(比如堆栈只由 int
组成),我们最终会得到这样的东西:
enum OpCode {
_iconst_1, _iadd
};
// ...
int* stack = new int[calculate_maximum_stack_size()];
size_t top_of_stack = 0;
size_t program_counter = 0;
while(program_counter < program_size) {
switch(opcodes[program_counter]) {
case _iconst_1:
// SET_STACK_INT(1, 0);
stack[top_of_stack] = 1;
// UPDATE_PC_AND_TOS_AND_CONTINUE(1, 1);
program_counter += 1;
top_of_stack += 1;
break;
case _iadd:
// SET_STACK_INT(VMintAdd(STACK_INT(-2), STACK_INT(-1)), -2);
stack[top_of_stack - 2] = stack[top_of_stack - 1] + stack[top_of_stack - 2];
// UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
program_counter += 1;
top_of_stack += -1;
break;
}
因此对于 1+1
,操作顺序为:
stack[0] = 1;
stack[1] = 1;
stack[0] = stack[1] + stack[0];
而 top_of_stack
将为 1,因此我们将以包含值 2
作为其唯一元素的堆栈结束。
请注意,我的问题是关于 JVM 解释器的,而不是 JIT 编译器。 JIT 编译器将 java 字节码转换为本机机器码。因此,这必须意味着 JVM 中的解释器不会将字节码转换为机器码。因此问题来了:解释器本质上是做什么的?如果有人可以用一个相当于 1+1 = 2 的字节码的简单示例来帮助我回答这个问题,即解释器在执行此添加操作时做了什么? (我隐含的问题是,如果解释器没有翻译成 CPU 然后执行 ADD 操作的机器代码,那么这个操作是如何执行的?实际执行什么机器代码来支持这个 ADD 操作?)
表达式1+1
将编译为以下字节码:
iconst_1
iconst_1
add
(实际上,它只会编译为 iconst_2
,因为 Java 编译器执行常量折叠,但为了这个答案的目的,让我们忽略它。)
所以要弄清楚解释器对那些指令究竟做了什么,我们应该分别看一下its source code. The relevant sections for const_1
and add
start at line 983 and line 1221,所以让我们看一下:
#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);
// goes on for several other constants
//...
#define OPC_INT_BINARY(opcname, opname, test) \
CASE(_i##opcname): \
if (test && (STACK_INT(-1) == 0)) { \
VM_JAVA_ERROR(vmSymbols::java_lang_ArithmeticException(), \
"/ by zero", note_div0Check_trap); \
} \
SET_STACK_INT(VMint##opname(STACK_INT(-2), \
STACK_INT(-1)), \
-2); \
UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1); \
// and then the same thing for longs instead of ints
OPC_INT_BINARY(add, Add, 0);
// other operators
整个事情都在检查当前指令的操作码的 switch 语句中。
如果我们扩展宏魔法,用一个极其简化的模板替换周围的代码,并做一些简化的假设(比如堆栈只由 int
组成),我们最终会得到这样的东西:
enum OpCode {
_iconst_1, _iadd
};
// ...
int* stack = new int[calculate_maximum_stack_size()];
size_t top_of_stack = 0;
size_t program_counter = 0;
while(program_counter < program_size) {
switch(opcodes[program_counter]) {
case _iconst_1:
// SET_STACK_INT(1, 0);
stack[top_of_stack] = 1;
// UPDATE_PC_AND_TOS_AND_CONTINUE(1, 1);
program_counter += 1;
top_of_stack += 1;
break;
case _iadd:
// SET_STACK_INT(VMintAdd(STACK_INT(-2), STACK_INT(-1)), -2);
stack[top_of_stack - 2] = stack[top_of_stack - 1] + stack[top_of_stack - 2];
// UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
program_counter += 1;
top_of_stack += -1;
break;
}
因此对于 1+1
,操作顺序为:
stack[0] = 1;
stack[1] = 1;
stack[0] = stack[1] + stack[0];
而 top_of_stack
将为 1,因此我们将以包含值 2
作为其唯一元素的堆栈结束。