JVM指令分析实例五(操作数栈)

栏目: Java · 发布时间: 6年前

内容简介:本篇为《JVM指令分析实例》的第五篇,相关实例均使用Oracle JDK 1.8编译,并使用javap生成字节码指令清单。前几篇传送门:

本篇为《JVM指令分析实例》的第五篇,相关实例均使用Oracle JDK 1.8编译,并使用javap生成字节码指令清单。

前几篇传送门:

JVM指令分析实例一(常量、局部变量、for循环)

JVM指令分析实例二(算术运算、常量池、控制结构)

JVM指令分析实例三(方法调用、类实例)

JVM指令分析实例四(数组、switch)

预备知识

局部变量表的变量槽(Variable Slot)

局部变量表的容量以变量槽(Variable Slot)为最小单位, 虚拟机规范中并没有明确指明一个Slot应占用的内存空间大小

每个Slot能存放一个boolean、byte、char、short、int、float、reference或returnAddress类型的数据。

reference类型表示对一个对象实例的引用, 虚拟机规范没有说明它的长度及结构

returnAddress类型目前已经很少见了,它是为字节码指令jsr、jsr_w和ret服务的,指向了一条字节码指令的地址。

对于64位的数据类型(long、double),虚拟机会以高位对齐的方式为其分配两个连续的Slot空间。

操作数栈管理指令

复制指令实例代码

package jvm.specification.se8.chapter3;
public class NextIndex {
    private long index = 0;
    public long nextIndex() {
        return index++;
    }
}

字节码指令序列

public long nextIndex():
0: aload_0  // 将第1个局部变量this压入栈顶
1: dup      // 复制栈顶this并压入栈顶. 栈底到栈顶:this、this
2: getfield #12 // Field index:J. 获取实例字段index并压入栈顶,消耗栈顶的1个this. 栈底到栈顶:this、index_for_ladd
5: dup2_x1  // 复制栈顶index数值,并插入第1个this下面. 栈底到栈顶:index_for_return、this、index_for_ladd
6: lconst_1 // 将long类型常量1压入栈顶
7: ladd     // 将栈顶的2个long类型数值相加,并将结果压入栈顶. 栈底到栈顶:index_for_lreturn、this、index_for_putfield
8: putfield #12 // Field index:J. 将栈顶数值赋值给实例字段index
11: lreturn

Constant pool:
   #1 = Class              #2             // jvm/specification/se8/chapter3/NextIndex
   #2 = Utf8               jvm/specification/se8/chapter3/NextIndex
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               index
   #6 = Utf8               J
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Methodref          #3.#11         // java/lang/Object."<init>":()V
  #11 = NameAndType        #7:#8          // "<init>":()V
  #12 = Fieldref           #1.#13         // jvm/specification/se8/chapter3/NextIndex.index:J
  #13 = NameAndType        #5:#6          // index:J

dup2_x1指令

复制栈顶的1个或2个值,并将其插入到栈顶的2个或3个值下面。

在预备知识中,我们对局部变量的Slot做了简单说明。可以简单理解为,long类型和double类型占2个Slot,其他类型占1个Slot。

下面拆解一下指令的定义(借用局部变量的Slot概念来描述,有点不太严谨,但易于理解与记忆。)。

复制栈顶的1个或2个值

1个可以是long类型和double类型,2个是其他类型,共2个Slot。

插入到栈顶的2个或3个值下面

如果复制的是1个值(即栈顶是long或double,共2个Slot),那么插入到栈顶的2个值(栈顶1个long或double,下面1个其他类型,共3个Slot)下面。

如果复制的是2个值(即栈顶是2个其他类型数值,共2个Slot),那么插入到栈顶的3个值(栈顶3个都是其他类型,共3个Slot)下面。

简单理解,dup2_x1指令的作用就是将栈顶的2个Slot的值复制并插入到栈顶的3个Slot的值下面。

对于本实例,执行dup2_x1指令之前的栈结构为(栈底到栈顶):this、index。由于index为long类型,占2个Slot。this为引用类型,占1个Slot。因此,dup2_x1指令将栈顶的2个Slot的index值复制并插入到栈顶的3个Slot的this引用下面。

操作数栈之指令系数法

dup总共有6个指令,分别是dup、dup_x1、dup_x2、dup2、dup2_x1和dup2_x2。初看这些指令,容易混淆而难以理解。 经过分类和找规律,可以通过"指令系数法"来理解记忆,非常简单:

  • 不带_x的指令是复制栈顶数据并压入栈顶。包括两个指令,dup和dup2
  • 带_x的指令是复制栈顶数据并插入栈顶以下的某个位置。共有4个指令
  • dup的系数代表要复制的Slot个数
    • dup开头的指令用于复制1个Slot的数据。例如1个int或1个reference类型数据
    • dup2开头的指令用于复制2个Slot的数据。例如1个long,或2个int,或1个int+1个float类型数据
  • 对于带_x的复制插入指令,只要将指令的dup和x的系数相加,结果即为需要插入的位置 。因此
    • dup_x1插入位置:1+1=2,即栈顶2个Slot下面。
    • dup_x2插入位置:1+2=3,即栈顶3个Slot下面。
    • dup2_x1插入位置:2+1=3,即栈顶3个Slot下面。
    • dup2_x2插入位置:2+2=4,即栈顶4个Slot下面。

操作数栈管理指令共有9个,上面已经介绍了6个。剩下的3个用同样的方法就很容易理解了:

  • pop:将栈顶的1个Slot数值出栈。例如1个short类型数值
  • pop2:将栈顶的2个Slot数值出栈。例如1个double类型数值,或者2个int类型数值
  • swap:交换栈顶的2个Slot数值位置。Java虚拟机没有提供交换两个64位数据类型(long、double)数值的指令。

备注:指令系数法是自己为了方便记忆起的名字

参考

《The Java Virtual Machine Specification, Java SE 8 Edition》

《Java虚拟机规范》(Java SE 8版)

《深入理解 Java 虚拟机 JVM高级特性与最佳实践》

转载请注明来源:http://zhanjia.iteye.com/blog/2432142

个人公众号

二进制之路

JVM指令分析实例五(操作数栈)


以上所述就是小编给大家介绍的《JVM指令分析实例五(操作数栈)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

产品的视角:从热闹到门道

产品的视角:从热闹到门道

后显慧 / 机械工业出版社 / 2016-1-1 / 69.00

本书在创造性的提出互联网产品定义的基础上,为读者提供了一个从0基础到产品操盘手的产品思维培养方法! 全书以互联网产品定义为基础,提出了产品思维学习的RAC模型,通过认识产品、还原产品和创造产品三个阶段去培养产品思维和产品认知。 通过大量的图片和视觉引导的方法,作者像零基础的用户深入浅出的描绘了一条产品经理的自我修养路径,并且提供了知识地图(knowledge map)和阅读雷达等工具,......一起来看看 《产品的视角:从热闹到门道》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

SHA 加密
SHA 加密

SHA 加密工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具