操作数栈(Operand Stack)
- 每一个独立的栈帧中除了包含局部变量表,还包含了一个后进先出(Last-In-Last-Out)的操作数栈,也可以称之为表达式栈(Expression Stack)
- 操作数栈,在方法执行的过程中,根据字节码指令,往栈中写入数据或者提取数据,即入栈(push)、出栈(pop)
- 某些字节码指令将值压入操作数栈,其余的字节码指令将操作数取出栈,使用后再把结果压入栈
- 比如:执行复制、交换、求和等操作
- 如果被调用的方法有返回值的话,其返回值将会被压入当前的操作数栈中,并更新PC寄存器中下一条需要执行的字节码指令
- 操作数栈中元素的数据类型必须与字节码指令的序列严格匹配,这由编译器在编译期间进行验证,同时在类加载过程中的类检验阶段的数据流分析阶段再次校验
- 另外我们说的Java虚拟机的解释引擎是基于栈的执行引擎,其中的栈指的就是操作数栈
- 操作数栈,主要用于保存计算过程的中间结果,同事作为计算过程中变量临时的存储空间
- 操作数栈就是JVM执行引擎的一个工作区,当一个新的方法开始执行的时候,一个新的栈帧随之被创建,这是该方法的操作数栈是空的
- 每一个操作数栈都会有一个明确的栈深度用于存储数值,其所需的最大深度在编译期就定义好了,保存在Code属性中,为stack的值
- 栈中的任何一个元素都是可以任意的Java数据类型
- 32bit的类型占用一个栈单位深度
- 64bit的类型占用两个栈单位的深度
- 操作数栈并非采用索引的方式来访问数的,而是只能通过标准的入栈(push)和出栈(pop)操作来完成一次数据的访问
代码演示
非常简单的一段代码,申明一个方法testAddOperand方法,返回在方法中申明了几个变量:i、j、k,右边是jclasslib编译后的字节码(IDEA安装jclasslib插件即可)
通过javap -verbose OperandStackTest.class我们就能查看到方法testAddOperand的操作数栈的最大深度为2,局部变量表的值为4(我们只申明了3个,但是在非静态的方法中还有一个this指向当前对象的变量,所以这里的local=4)
操作步骤:
bipush,将15压入操作数栈的栈顶,此时pc寄存器指向0
istore_1操作,将操作数栈中栈顶15弹出,放到局部变量表中,位置为1,此时pc寄存器在2的位置
bipush 操作,将8压入操作数栈的栈顶,此时pc寄存器指向3
istore_2操作,将操作数栈中栈顶8弹出,放到局部变量表中,位置为2,此时pc寄存器在5的位置
iload_1, iload_2,iadd操作:将局部变量表中位置1和位置2的数值取出进行一个加操作,将结构压入操作数栈,此时pc寄存器8的位置
istore_3操作:将操作数栈栈顶的23加载道局部变量表3的位置,此时pc寄存器9的位置
最后该方法没有返回值,retrun结束