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

前几篇传送门:

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

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

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

数组

一维原始类型数组

void createBuffer() {int buffer[];int bufsz = 100;int value = 12;buffer = new int[bufsz];buffer[10] = value;value = buffer[11];
}
复制代码

字节码指令序列

void createBuffer():0: bipush        100   // 将单字节int常量值100压入栈顶2: istore_2            // 将栈顶int类型数值100存入第3个局部变量. bufsz = 1003: bipush        12    // 将单字节int常量值12压入栈顶5: istore_3            // 将栈顶int类型数值12存入第4个局部变量. value = 126: iload_2             // 将第3个int类型局部变量压入栈顶7: newarray       int  // 创建int类型数组,并将数组引用值压入栈顶. new int[bufsz]9: astore_1            // 将栈顶引用类型值存入第2个局部变量. buffer = new int[bufsz]
10: aload_1             // 将第2个引用类型局部变量压入栈顶
11: bipush        10    // 将单字节int常量10压入栈顶
13: iload_3             // 将第4个int类型局部变量压入栈顶
14: iastore             // 将栈顶int类型数值存入数组的指定索引位置. buffer[10] = value
15: aload_1             // 将第2个引用类型值压入栈顶
16: bipush        11    // 将单字节int常量值11压入栈顶
18: iaload              // 将int类型数组的指定元素压入栈顶
19: istore_3            // 将栈顶int类型数值存入第4个局部变量
20: return
复制代码

newarray指令

创建一个指定原始类型(如int、float、char等)的数组,并将其引用值压入栈顶。

执行该指令后,将从操作数栈出栈1个参数count,类型为int,表示要创建数组的大小。

iastore指令

从操作数栈读取一个int类型数据并存入指定数组中。

执行该指令后,将从操作数栈出栈3个参数arrayref、index和value,在本例中分别对应于第10、11和13索引位置压入的值。

其中,arrayref是一个引用类型值,指向一个int类型的数组。index和value为int类型,index表示待存入数组位置的索引号,value表示待存入index索引位置的值。

iaload指令

从数组中加载一个int类型数据到操作数栈。

执行该指令后,将从操作数栈出栈2个参数arrayref和index,在本例中分别对应于第15和16索引位置压入的值。

其中,arrayref是一个引用类型值,指向一个int类型的数组。index为int类型,表示待加载数组数据的索引号。

一维引用类型数组

void createThreadArray() {Thread threads[];int count = 10;threads = new Thread[count];threads[0] = new Thread();
}
复制代码

字节码指令序列

void createThreadArray():0: bipush        10    // 将单字节int类型值10压入栈顶2: istore_2            // 将栈顶int类型值存入第3个局部变量. count = 103: iload_2             // 将第3个int类型局部变量压入栈顶4: anewarray     #15   // class java/lang/Thread. 创建Thread类型数组,并将数组引用值压入栈顶. new Thread[count]7: astore_1            // 将栈顶引用类型值存入第2个局部变量8: aload_1             // 将第2个引用类型局部变量压入栈顶9: iconst_0            // 将int类型常量0压入栈顶
10: new           #15   // class java/lang/Thread. 创建Thread对象,并将引用值压入栈顶
13: dup                 // 复制栈顶值并压入栈顶
14: invokespecial #17   // Method java/lang/Thread."<init>":()V. 调用实例初始化方法
17: aastore             // 将栈顶引用类型值存入数组的指定索引位置. threads[0] = new Thread()
18: return
复制代码

anewarray指令

创建一个引用类型(如类、接口、数组)数组,并将其引用值压入栈顶。可用于创建一维引用数组,或者用于创建多维数组的一部分

执行该指令后,将从操作数栈出栈1个参数count,类型为int,表示要创建数组的大小。

aastore指令

(aastore指令与iastore指令作用类似)

从操作数栈读取一个引用类型数据并存入指定数组中。

执行该指令后,将从操作数栈出栈3个参数arrayref、index和value,在本例中分别对应于第8、9和10索引位置压入的值。

其中,arrayref是一个引用类型值,指向一个引用类型的数组。index为int类型,index表示待存入数组位置的索引号。value为引用类型,表示待存入index索引位置的值。

在运行时,value的实际类型必须与arrayref所代表的数组的组件类型相匹配。

多维数组

int[][][] create3DArray() {int grid[][][];grid = new int[10][5][];return grid;
}
复制代码

字节码指令序列

int[][][] create3DArray():
0: bipush        10         // 将单字节int类型值10压入栈顶. 第1维
2: iconst_5                 // 将int类型常量5压入栈顶. 第2维
3: multianewarray #16,  2   // class "[[[I". 创建int[][][]类型数组,并将引用值压入栈顶
7: astore_1                 // 将栈顶引用类型值存入第2个局部变量
8: aload_1                  // 将第2个引用类型局部变量压入栈顶
9: areturn                  // 从当前方法返回栈顶引用类型值
复制代码

multianewarray指令

创建指定类型和指定维度的多维数组(执行该指令时,操作数栈中必须包含各维度的长度值),并将其引用值压入栈顶。可以用于创建所有类型的多维数组

对于本实例,数组类型为[[[I,即#16对应的常量池中的符号引用。数组维度为2,两个维度的长度值分别为10和5。虽然int[][][]为3维数组,但由于仅指定了前2个维度的长度值,因此指令对应的维度值为2。

如果指定了第3个维度的长度值,那么在iconst_5之后还需要再将1个int类型长度值压入栈。

所有的数组都有一个与之关联的长度属性,可通过arraylength指令访问。

switch语句

编译器会使用tableswitch和lookupswitch指令来生成switch语句的编译代码。

Java虚拟机的tableswitch和lookupswitch指令都只能支持int类型的条件值。

tableswitch指令可以高效地从索引表中确定case语句块的分支偏移量。

当switch语句中的case分支条件值比较稀疏时,tableswitch指令的空间使用率偏低。这种情况下,可以使用lookupswitch指令来代替。

tableswitch指令

int chooseNear(int i) {switch(i) {case 0: return 0;case 1: return 1;case 2: return 2;default: return -1;}
}
复制代码

字节码指令序列

int chooseNear(int):0: iload_1         // 将第2个int类型局部变量压入栈顶1: tableswitch   { // 0 to 20: 28    // 如果case条件值为0,则跳转到索引号为28的指令继续执行1: 30    // 如果case条件值为1,则跳转到索引号为30的指令继续执行2: 32    // 如果case条件值为2,则跳转到索引号为32的指令继续执行default: 34    // 否则,则跳转到索引号为34的指令继续执行}
28: iconst_0        // 将int类型常量0压入栈顶
29: ireturn         // 从当前方法返回栈顶int类型数值
30: iconst_1        // 将int类型常量1压入栈顶
31: ireturn         // 从当前方法返回栈顶int类型数值
32: iconst_2        // 将int类型常量2压入栈顶
33: ireturn         // 从当前方法返回栈顶int类型数值
34: iconst_m1       // 将int类型常量-1压入栈顶
35: ireturn         // 从当前方法返回栈顶int类型数值
复制代码

tableswitch指令

用于switch条件跳转,case值连续(变长指令)。

根据索引值在跳转表中寻找配对的分支并进行跳转。

指令格式:tableswitch padbytes defaultbytes lowbytes highbytes jumptablebytes

  • padbytes:0~3个填充字节,以使得defaultbytes与方法起始地址(方法内第一条指令的操作码所在的地址)之间的距离是4的位数。
  • defaultbytes:32位默认跳转地址
  • lowbytes:32位低值low
  • highbytes:32位高值high
  • jumptablebytes:(high-low+1)个32位有符号数值形成的一张零基址跳转表(0-based jump table)

由于采用了索引值定位的方式(可理解为数组随机访问),因此只需要检查索引是否越界,非常高效。

下面结合实例分析一下:

第1条指令的索引号为0,tableswitch指令索引号为1,为了使defaultbytes与方法起始地址之间的距离是4的位数,所以defaultbytes的开始索引号为4。

defaultbytes、lowbytes和highbytes分别占4个字节,总共12个字节。

case高低值分别为2和0,因此jumptablebytes占用(2-0+1)*4=12个字节。

由于defaultbytes的开始索引号为4,defaultbytes~jumptablebytes共占用24个字节,因此紧跟在tableswitch后面的下一条指令的索引号为4+24=28,对应于实例中的指令"28: iconst_0"。

这里顺便提一下,一般情况下,普通的操作数占1个字节,指向常量池的索引值占2个字节(ldc的常量池索引占1个字节,ldc_w、ldc2_w的常量池索引占2个字节)。所以,方法的指令索引号之间有时不是连续的

lookupswitch指令

int chooseFar(int i) {switch(i) {case -100: return -1;case 0: return 0;case 100: return 1;default: return -1;}
}
复制代码

字节码指令序列

int chooseFar(int):0: iload_11: lookupswitch  { // 3-100: 360: 38100: 40default: 42}
36: iconst_m1
37: ireturn
38: iconst_0
39: ireturn
40: iconst_1
41: ireturn
42: iconst_m1
43: ireturn
复制代码

lookupswitch指令

用于switch条件跳转,case值不连续(变长指令)。

根据键值(非索引)在跳转表中寻找配对的分支并进行跳转。

指令格式:lookupswitch padbytes defaultbytes npairsbytes matchoffsetbytes

  • padbytes:0~3个填充字节,以使得defaultbytes与方法起始地址(方法内第一条指令的操作码所在的地址)之间的距离是4的位数。
  • defaultbytes:32位默认跳转地址
  • npairsbytes:32位匹配键值对的数量npairs
  • matchoffsetbytes:npairs个键值对,每一组键值对都包含了一个int类型值match以及一个有符号32位偏移量offset。

由于case条件值是非连续的,因此无法采用像tableswitch直接定位的方式,必须对每个键值进行比较。然而,JVM规定,lookupswitch的跳转表必须根据键值排序,这样(如采用二分查找)会比线性扫描更有效率。

下面结合实例分析一下:

第1条指令的索引号为0,lookupswitch指令索引号为1,为了使defaultbytes与方法起始地址之间的距离是4的位数,所以defaultbytes的开始索引号为4。

defaultbytes、npairsbytes分别占4个字节,总共8个字节。

case有3个条件,共3个键值对(npairs为3)。由于每个键值对占8个字节(4字节match+4字节offset),因此matchoffsetbytes共占24个字节。

所以,紧跟在lookupswitch后面的下一条指令的索引号为4+8+24=36,对应于实例中的指令"36: iconst_m1"。


题图:codeforwin.org

参考

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

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

JVM指令分析实例四(数组、switch)相关推荐

  1. 实例四:switch分支选择——实现阶梯式计算

    实例四:switch分支选择--实现阶梯式计算 问题描述: 假设公民交纳个人所得税的税率如下(a 代表个人收入,r 代表税率) r = 0 (a < 2000元) r = 5% (2000元 & ...

  2. JVM内存区域:递归JVM指令分析

  3. 【Java 虚拟机原理】栈帧 | 局部变量表 | 操作数栈 | 方法出口 | JVM 指令逐条解析

    文章目录 前言 一.JVM 指令逐条解析 1.Java 代码 2.Java 虚拟机指令 3.分析 JVM 指令 4.局部变量表 与 操作数栈 二.方法出口 前言 " 栈帧 " 中存 ...

  4. JVM从入门到精通(四):内存屏障与JVM指令,对象的内存布局

    JMM 硬件层数据一致性 协议很多,intel 用MESI https://www.cnblogs.com/z00377750/p/9180644.html 现代CPU的数据一致性实现 = 缓存锁(M ...

  5. 爬虫系列教程四:动态网页api分析实例之爬取dropbox上的pdf

    动态网页api分析实例:爬取dropbox上的pdf 动态网页api分析实例:爬取dropbox上的pdf 任务 分析 写代码和debug 今天老师让我下载一个网课里面的pdf材料,pdf的数目比较多 ...

  6. 通过javap命令分析jvm指令

    王二北原创,转载请标明出处:来自王二北 一.javap命令简述 javap是jdk自带的反解析工具.它的作用就是根据class字节码文件,反解析出当前类对应的code区(汇编指令).本地变量表.异常表 ...

  7. Java并发编程(五)JVM指令重排

    我是不是学了一门假的java...... 引言:在Java中看似顺序的代码在JVM中,可能会出现编译器或者CPU对这些操作指令进行了重新排序:在特定情况下,指令重排将会给我们的程序带来不确定的结果.. ...

  8. jvm内存模型_四种视角看JVM内存模型

    1.JVM运行视角 程序计数器 Java虚拟机栈 本地方法栈 Java堆 方法区           1 .程序计数器         程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的行号 ...

  9. 第七章:小朱笔记hadoop之源码分析-hdfs分析 第四节:namenode分析-namenode启动过程分析...

    第七章:小朱笔记hadoop之源码分析-hdfs分析 第四节:namenode分析 4.1 namenode启动过程分析 org.apache.hadoop.hdfs.server.namenode. ...

最新文章

  1. 在AcGIS随着大数据的生成DEM
  2. 互联网轻量级框架SSM-查缺补漏第二天
  3. varint算法——本质上是牺牲最高位作为标识数据结束位,达到变长编码,说白了就是贪心的分割位...
  4. windows 10 家庭版 无法打开 gpedit.msc 解决方法
  5. 聚类 python 代码_不足 20 行 Python 代码,高效实现 k-means 均值聚类算法
  6. python层次聚类法画图_Python实现简单层次聚类算法以及可视化
  7. Jsp中Uploadify插件的使用(jQuery上传插件)
  8. c++ operator操作符的两种用法:重载和隐式类型转换,string转其他基本数据类型的简洁实现string_cast...
  9. 在.NET2.0中上传文件操作(解决了上传文件大小和多文件限制)--转
  10. fbinstool linux iso,大神给你传授fbinsttool下载 【操作教程】 的详细_
  11. unity直播推流方式_干货,抖音无人直播技术(建议收藏)
  12. excel求方差和标准差的函数_Excel公式和函数 方差和标准差
  13. 黑马程序员----------Java新特性反射 泛型
  14. Unity-两张图片叠加合成一张图片
  15. FTP文件传输协议详解
  16. Linux内核设计与实现 第19章 可移植性
  17. 网约车收费器设计(lunwen+任务书+翻译及原文+答辩PPT+程序+原理图)
  18. windows有哪些版本
  19. 图像卷积原理及MATLAB实现
  20. GBase 8s与Oracle对比分析

热门文章

  1. 六大基本AI术语:如何做好人工智能咨询服务?
  2. SAP PP ECR的Profile规定了用它可以修改哪些数据对象
  3. 谷歌发布TensorFlow,用于测试人工智能模型的隐私保护
  4. 了解机器学习回归的3种最常见的损失函数
  5. 人工智能激活千亿级交通安全市场
  6. 机器学习算法工程师的自我修养
  7. 机器学习与推荐系统实践
  8. 机器学习里如何确定K-Means算法的K值?
  9. CNN在中文文本分类的应用
  10. 《数学之美》第12章 有限状态机—地图与本地搜索的核心技术