1.8 JVM的架构模型
Java编译器输入的指令流是一种基于栈的指令集架构,另外一种指令集架构则是基于寄存器的指令集架构。具体来说,这两种架构之间的区别如下。
1.基于栈式架构的特点
(1)设计和实现更简单,适用于资源受限的系统。比如机顶盒、打印机等嵌入式设备。
(2)避开了寄存器的分配难题,使用零地址指令方式分配,只针对栈顶元素操作。
(3)指令流中的指令大部分是零地址指令,其执行过程依赖操作栈。指令集更小,编译器更容易实现。
(4)不需要硬件支持,可移植性更好,可以更好地实现跨平台。
2.基于寄存器架构的特点
(1)典型的应用是x86的二进制指令集。比如传统的PC以及Android的Davlik虚拟机。
(2)指令集架构则完全依赖硬件,可移植性差。
(3)指令直接由CPU来执行,性能优秀和执行更高效。
(4)花费更少的指令去完成一项操作。
(5)在大部分情况下,基于寄存器架构的指令集往往都以一地址指令、二地址指令和三地址指令为主,而基于栈式架构的指令集却是以零地址指令为主。
案例1:执行2+3这种逻辑操作,其代码示例如下:
基于栈的计算流程(以JVM为例):
整个执行流程如下:
(1)常量2压入操作数栈(见4.4节)。
(2)弹出操作数栈栈顶元素,保存到局部变量表(见4.3节)第1个位置(把常量2保存到局部变量表)。
(3)常量3压入操作数栈。
(4)弹出操作数栈栈顶元素,保存到局部变量表第2个位置(把常量3保存到局部变量表)。
(5)变量表中第1个变量压入操作数栈。
(6)变量表中第2个变量压入操作数栈。
(7)操作数栈中的前两个int相加,并将结果压入操作数栈栈顶。
(8)弹出操作数栈栈顶元素,保存到局部变量表第0个位置(把结果5保存到局部变量表)。
基于寄存器的计算流程如下:
mov eax,2 //将eax寄存器的值设为2 add eax,3 //将eax寄存器的值加3
接下来详细讲解一下基于栈的计算流程,案例2是在案例1的类中新增calc()方法。案例2代码如下:
反编译后的结果如下:
字节码文件的反编译命令是javap(见17.4节),例如使用下面的命令反编译StackStruTest.class文件:javap -v StackStruTest.class。具体流程如图1-10~图1-16所示。
指令执行流程如下:
(1)常量100压入操作数栈(见4.4节)。
(2)弹出操作数栈栈顶元素,保存到局部变量表(见4.3节)第1个位置(把常量100保存到局部变量表)。
(3)常量200压入操作数栈。
(4)弹出操作数栈栈顶元素,保存到局部变量表第2个位置(把常量200保存到局部变量表)。
图1-10 案例2字节码指令执行流程图(1)
图1-11 案例2字节码指令执行流程图(2)
图1-12 案例2字节码指令执行流程图(3)
图1-13 案例2字节码指令执行流程图(4)
图1-14 案例2字节码指令执行流程图(5)
图1-15 案例2字节码指令执行流程图(6)
图1-16 案例2字节码指令执行流程图(7)
(5)常量300压入操作数栈。
(6)弹出操作数栈栈顶元素,保存到局部变量表第3个位置(把常量300保存到局部变量表)。
(7)变量表中第1个变量压入操作数栈。
(8)变量表中第2个变量压入操作数栈。
(9)操作数栈中的前两个int相加,并将结果压入操作数栈栈顶。
(10)变量表中第3个变量压入操作数栈。
(11)操作数栈中的前两个int相乘,并将结果压入操作数栈栈顶。
(12)弹出操作数栈栈顶元素,保存到局部变量表第0个位置(把结果90000保存到局部变量表)。
我们来总结一下,由于跨平台性的设计,Java的指令都是根据栈来设计的。不同平台CPU架构不同,所以不能设计为基于寄存器的。基于栈式架构的优点是跨平台、指令集小、编译器容易实现,缺点是性能较差,实现同样的功能需要更多的指令。
时至今日,尽管嵌入式平台已经不是Java程序的主流运行平台(准确来说,应该是HotSpotVM的宿主环境已经不局限于嵌入式平台),那么为什么不将架构更换为基于寄存器的架构呢?
首先基于栈的架构从设计和实现上更简单一些,如入栈出栈等方面;其次在非资源受限的平台当中也是可用的,即体现了它的跨平台性。