Java虚拟机的执行引擎在执行Java代码的时候都有解释执行(通过解释器执行) 和编译执行(通过即时编译器产生本地代码执行) 两种选择。
解释执行
Java语言经常被人们定位为“解释执行”的语言,但主流的虚拟机中都包含了即时编译器后,Class文件中的代码到底会被解释执行还是编译执行,就成了只有虚拟机自己才能准确判断的事情。
编译过程
Java语言中,Javac编译器完成了程序代码经过词法分析、语法分析到抽象语法树,再遍历语法树生成线性的字节码指令流的过程。因为这一部分动作是在Java虚拟机之外进行的,而解释器在虚拟机的内部,所以Java程序的编译就是半独立的实现
基于栈的指令集与基于寄存器的指令集
Java编译器输出的指令流,基本上是一种基于栈的指令集架构(Instruction SetArchitecture,ISA),指令流中的指令大部分都是零地址指令,它们依赖操作数栈进行工作。
与之相对的另外一套常用的指令集架构是基于寄存器的指令集,最典型的就是x86的二地址指令集,说得通俗一些,就是现在我们主流PC机中直接支持的指令集架构,这些指令依赖寄存器进行工作。
使用基于栈的指令集计算“1+1”
iconst_1
iconst_1
iadd
istore_0
两条iconst_1指令连续把两个常量1压入栈后,iadd指令把栈顶的两个值出栈、相加,然后把结果放回栈顶,最后istore_0把栈顶的值放到局部变量表的第0个Slot中。
使用基于寄存器的指令集计算“1+1”
mov eax,1
add eax,1
mov指令把EAX寄存器的值设为1,然后add指令再把这个值加1,结果就保存在EAX寄存器里面。
基于栈的指令集主要的优点就是可移植,寄存器由硬件直接提供,程序直接依赖这些硬件寄存器则不可避免地要受到硬件的约束。栈架构指令集的主要缺点是执行速度相对来说会稍慢一些。所有主流物理机的指令集都是寄存器架构也从侧面印证了这一点。
基于栈的解释器执行过程
一段简单的算术代码
public int calc(){
int a=100;
int b=200;
int c=300;
return(a+b)*c;
}
使用javap命令查看字节码指令
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个Slot的局部变量空间。
执行偏移地址为0的指令的情况
执行偏移地址为1的指令的情况
执行偏移地址为11的指令的情况
执行偏移地址为12的指令的情况
执行偏移地址为13的指令的情况
执行偏移地址为14的指令的情况
执行偏移地址为16的指令的情况
上面的执行过程仅仅是一种概念模型,虚拟机最终会对执行过程做一些优化来提高性能,实际的运作过程不一定完全符合概念模型的描述……更准确地说,实际情况会和上面描述的概念模型差距非常大,这种差距产生的原因是虚拟机中解析器和即时编译器都会对输入的字节码进行优化。