代码改变世界

操作数栈

2017-11-18 20:00  shizhiyi  阅读(12279)  评论(1编辑  收藏  举报

 操作数栈

每一个独立的栈帧中除了包含局部变量表以外,还包含一个后进先出(Last-In-First-Out)的操作数栈,也可以称之为表达式栈(Expression Stack)。操作数栈和局部变量表在访问方式上存在着较大差异,操作数栈并非采用访问索引的方式来进行数据访问的,而是通过标准的入栈和出栈操作来完成一次数据访问。每一个操作数栈都会拥有一个明确的栈深度用于存储数值,一个32bit的数值可以用一个单位的栈深度来存储,而2个单位的栈深度则可以保存一个64bit的数值,当然操作数栈所需的容量大小在编译期就可以被完全确定下来,并保存在方法的Code属性中。

在HotSpot中,除了PC寄存器之外,再也没有包含其他任何的寄存器,并且之前曾经提及过,HotSpot中任何的操作都需要经过入栈和出栈来完成,那么由此可见,HotSpot的执行引擎架构必然就是基于栈式架构,而非传统的寄存器架构。简单来说,操作数栈就是JVM执行引擎的一个工作区,当一个方法被调用的时候,一个新的栈帧也会随之被创建出来,但这个时候栈帧中的操作数栈却是空的,只有方法在执行的过程中,才会有各种各样的字节码指令往操作数栈中执行入栈和出栈操作。比如在一个方法内部需要执行一个简单的加法运算时,首先需要从操作数栈中将需要执行运算的两个数值出栈,待运算执行完成后,再将运算结果入栈。如下所示:

代码8-2  执行加法运算的字节码指令

  1. public void testAddOperation();  
  2.         Code:  
  3.           0: bipush        15  
  4.      2: istore_1  
  5.      3: bipush        8  
  6.      5: istore_2  
  7.      6: iload_1  
  8.      7: iload_2  
  9.      8: iadd  
  10.      9: istore_3  
  11.      10: return 

在上述字节码指令示例中,首先会由“bipush”指令将数值15从byte类型转换为int类型后压入操作数栈的栈顶(对于byte、short和char类型的值在入栈之前,会被转换为int类型),当成功入栈之后,“istore_1”指令便会负责将栈顶元素出栈并存储在局部变量表中访问索引为1的Slot上。接下来再次执行“bipush”指令将数值8压入栈顶后,通过“istore_2”指令将栈顶元素出栈并存储在局部变量表中访问索引为2的Slot上。“iload_1”和“iload_2”指令会负责将局部变量表中访问索引为1和2的Slot上的数值15和8重新压入操作数栈的栈顶,紧接着“iadd”指令便会将这2个数值出栈执行加法运算后再将运算结果重新压入栈顶,“istore_3”指令会将运算结果出栈并存储在局部变量表中访问索引为3的Slot上。最后“return”指令的作用就是方法执行完成之后的返回操作。在操作数栈中,一项运算通常由多个子运算(subcomputation)嵌套进行,一个子运算过程的结果可以被其他外围运算所使用。

在此大家需要注意,在操作数栈中的数据必须进行正确的操作。比如不能在入栈2个int类型的数值后,却把它们当做long类型的数值去操作,或者入栈2个double类型的数值后,使用iadd指令对它们执行加法运算等情况出现。