![剑指JVM:虚拟机实践与性能调优](https://wfqqreader-1252317822.image.myqcloud.com/cover/787/52842787/b_52842787.jpg)
1.8 JVM的架构模型
Java编译器输入的指令流是一种基于栈的指令集架构,另外一种指令集架构则是基于寄存器的指令集架构。具体来说,这两种架构之间的区别如下。
1.基于栈式架构的特点
(1)设计和实现更简单,适用于资源受限的系统。比如机顶盒、打印机等嵌入式设备。
(2)避开了寄存器的分配难题,使用零地址指令方式分配,只针对栈顶元素操作。
(3)指令流中的指令大部分是零地址指令,其执行过程依赖操作栈。指令集更小,编译器更容易实现。
(4)不需要硬件支持,可移植性更好,可以更好地实现跨平台。
2.基于寄存器架构的特点
(1)典型的应用是x86的二进制指令集。比如传统的PC以及Android的Davlik虚拟机。
(2)指令集架构则完全依赖硬件,可移植性差。
(3)指令直接由CPU来执行,性能优秀和执行更高效。
(4)花费更少的指令去完成一项操作。
(5)在大部分情况下,基于寄存器架构的指令集往往都以一地址指令、二地址指令和三地址指令为主,而基于栈式架构的指令集却是以零地址指令为主。
案例1:执行2+3这种逻辑操作,其代码示例如下:
![](https://epubservercos.yuewen.com/2DBB65/31398172904524506/epubprivate/OEBPS/Images/Figure-P20_32285.jpg?sign=1739010059-X6itxWZazTdaZMaL0Yzq5nce6s7Opq0q-0-c58546aeffd8699dc2756896ee5516ef)
基于栈的计算流程(以JVM为例):
![](https://epubservercos.yuewen.com/2DBB65/31398172904524506/epubprivate/OEBPS/Images/Figure-P20_703.jpg?sign=1739010059-9ptZthj3MtQEGYah9DhsG8ByOvjOIYUv-0-e0d78f50e638767c0f663212f61a47bd)
整个执行流程如下:
(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代码如下:
![](https://epubservercos.yuewen.com/2DBB65/31398172904524506/epubprivate/OEBPS/Images/Figure-P21_32289.jpg?sign=1739010059-MKs5yk3F4gwiU08pUMwgNHvjUlNAhhdL-0-4e507b743ae0519f1a3c4a77e55d4253)
反编译后的结果如下:
![](https://epubservercos.yuewen.com/2DBB65/31398172904524506/epubprivate/OEBPS/Images/Figure-P21_32291.jpg?sign=1739010059-JXLNwkNFA19AtpZ6eR0jUeDpqnYGMvhG-0-e7a99cc1c215ffe36f9e860454e06f7b)
字节码文件的反编译命令是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保存到局部变量表)。
![](https://epubservercos.yuewen.com/2DBB65/31398172904524506/epubprivate/OEBPS/Images/Figure-P22_767.jpg?sign=1739010059-5mTUNxp7SylFADZi0xhcvOAHUjwIAr70-0-39ca2e526472b7f1830c152a7f44d0a0)
图1-10 案例2字节码指令执行流程图(1)
![](https://epubservercos.yuewen.com/2DBB65/31398172904524506/epubprivate/OEBPS/Images/Figure-P22_768.jpg?sign=1739010059-cnJ8DCeLfVJxfKslWRFGySF6r2h2txtu-0-f375da86c9ca7d0e5cbfeb3ad6ca3d3f)
图1-11 案例2字节码指令执行流程图(2)
![](https://epubservercos.yuewen.com/2DBB65/31398172904524506/epubprivate/OEBPS/Images/Figure-P22_771.jpg?sign=1739010059-5ZxZDxWYOMuAZ8396NAER0WVRscXhqPf-0-4b88ff2cc54d10c358196edc064cb811)
图1-12 案例2字节码指令执行流程图(3)
![](https://epubservercos.yuewen.com/2DBB65/31398172904524506/epubprivate/OEBPS/Images/Figure-P22_772.jpg?sign=1739010059-iYfgZgLeGOTHQEuEQes3W7VDDDHNvm8I-0-5bd610cda3f20a4c6e978a64728eb150)
图1-13 案例2字节码指令执行流程图(4)
![](https://epubservercos.yuewen.com/2DBB65/31398172904524506/epubprivate/OEBPS/Images/Figure-P22_775.jpg?sign=1739010059-PUAeTwAB9BDBfp2XYHqUxlf5e0Fzs2mx-0-e0e6777a7eac5262829d748fef220829)
图1-14 案例2字节码指令执行流程图(5)
![](https://epubservercos.yuewen.com/2DBB65/31398172904524506/epubprivate/OEBPS/Images/Figure-P22_776.jpg?sign=1739010059-7b4Kt1U6QMkHmJTc0CpQ8MKRdoo3Ieu0-0-65d23c812ed68736761e395f8439c413)
图1-15 案例2字节码指令执行流程图(6)
![](https://epubservercos.yuewen.com/2DBB65/31398172904524506/epubprivate/OEBPS/Images/Figure-P23_784.jpg?sign=1739010059-rYETXRRwrDHxMe4FgENqofg5xPAk4P46-0-3b08cfe4bd2ebced81cd19bbdd738f70)
图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的宿主环境已经不局限于嵌入式平台),那么为什么不将架构更换为基于寄存器的架构呢?
首先基于栈的架构从设计和实现上更简单一些,如入栈出栈等方面;其次在非资源受限的平台当中也是可用的,即体现了它的跨平台性。