解释执行

Java语言经常被人们定位为“解释执行”的语言,但当主流的虚拟机中都包含了即时编译器后,Class文件中的代码到底会被解释执行还是编译

执行,就成了只有虚拟机自己才能准确判断的事。

大部分的程序代码转换成物理机的目标代码或虚拟机能执行的指令集

之前,都需要经过图8-4中的各个步骤。

在Java语言中,Javac编译器完成了程序代码经过词法分析、语法分析到抽象语法树,再遍历语法

树生成线性的字节码指令流的过程。因为这一部分动作是在Java虚拟机之外进行的,而解释器在虚拟

机的内部,所以Java程序的编译就是半独立的实现。

基于栈的解释器执行过程

Javac编译器输出的字节码指令流,基本上[1]是一种基于栈的指令集架构(Instruction Set

Architecture,ISA),字节码指令流里面的指令大部分都是零地址指令,它们依赖操作数栈进行工

作。与之相对的另外一套常用的指令集架构是基于寄存器的指令集,最典型的就是x86的二地址指令

集,如果说得更通俗一些就是现在我们主流PC机中物理硬件直接支持的指令集架构,这些指令依赖寄

存器进行工作。

public int calc() {
	int a = 100;
	int b = 200;
	int c = 300;
	return (a + b) * c;
}
public int calc();
	Code:
	Stack=2, Locals=4, Args_size=1
	0: bipush 100
	2: istore_1
	3: sipush 200
	6: istore_2
	7: sipush 300
	10: istore_3
	11: iload_1
	12: iload_2
	13: iadd
	14: iload_3
	15: imul
	16: ireturn
}

javap提示这段代码需要深度为2的操作数栈和4个变量槽的局部变量空间。

Bipush指令的作用是将单字节的整型常量值(-128~127)推入

操作数栈顶,跟随有一个参数,指明推送的常量值,这里是100。

istore_1指令的作用是将操作数栈顶的整型值出栈并存放到第1个局部变

量槽中。后续4条指令(直到偏移为11的指令为止)都是做一样的事情,也就是在对应代码中把变量

a、b、c赋值为100、200、300。

iload_1指令的作用是将局部变量表第1个变量槽中的整型值复制到操作

数栈顶。

iadd指令的作用是将操作数栈中头两个栈顶元素出栈,做整型加法,

然后把结果重新入栈。在iadd指令执行完毕后,栈中原有的100和200被出栈,它们的和300被重新入

栈.

iload_3指令把存放在第3个局部变量槽中的300入栈到操作数栈中。这

时操作数栈为两个整数300。下一条指令imul是将操作数栈中头两个栈顶元素出栈,做整型乘法,然后

把结果重新入栈,与iadd完全类似.

ireturn指令是方法返回指令之一,它将结束方法执行并将操作数栈顶

的整型值返回给该方法的调用者。到此为止,这段方法执行结束。

再次强调上面的执行过程仅仅是一种概念模型,虚拟机最终会对执行过程做出一系列优化来提高

性能,实际的运作过程并不会完全符合概念模型的描述。更确切地说,实际情况会和上面描述的概念

模型差距非常大,差距产生的根本原因是虚拟机中解析器和即时编译器都会对输入的字节码进行优

化,即使解释器中也不是按照字节码指令去逐条执行的。例如在HotSpot虚拟机中,就有很多

以“fast_”开头的非标准字节码指令用于合并、替换输入的字节码以提升解释执行性能,即时编译器的

优化手段则更是花样繁多[1]