一. 前置内容
本篇文章是对JVM 指令集的详解,为了防止读者没有接触过这方面内容,对读懂指令集的前置知识做一个简单介绍。
1.1 数据类型
众所周知,java 的数据类型分为两种,原始类型和引用类型,但是在 JVM 中还是有一点小区别的。
1.1.1 原始类型
在
JVM中原始类型包括三种 数值类型(numeric types),boolean类型和returnAddress类型。
1.1.1.1 数值类型(numeric types)
. byte 表示8 位有符号二进制整数,默认值是 0。
. short 表示16 位有符号二进制整数,默认值是 0。
. int 表示32 位有符号二进制整数,默认值是 0。
. long 表示64 位有符号二进制整数,默认值是 0。
. char 表示16 位无符号二进制整数,默认值是 '\u0000'。
. float 表示32 位单精度浮点数,默认值是 0。
. double 表示64 位双精度浮点数,默认值是 0。
1.1.1.2 boolean 类型
在 JVM 中boolean 类型其实就是用 int 类型代替,int 类型的 0 表示 false,int 类型的 1 表示 true。
可能有读者疑惑了,为啥不用
byte类型代替呢,这样更节省空间;其实接下来你就能明白,在JVM的虚拟机栈中,byte,short,char和boolean全部都当成int类型操作的。
1.1.1.3 returnAddress 类型
这个在 java 语言中没有对应的类型,与 jsr, ret 和 jsr_w 这三个指令有关,原来是用于处理异常跳转和 finally 语句的,但是 JVM7 之后,已经不使用这三个指令处理异常跳转和 finally 语句,也就是说你也看不到这个类型了。
1.1.2 引用类型
JVM 中引用类型分为三种 类类型(class types),数组类型(array types)和接口类型(interface types)。
引用类型有一个特殊值
null, 当引用类型不指向任何对象时,它的值就是null;引用类型默认值就是null。
1.2 运行时数据区

- 虚拟机栈(
Java Virtual Machine Stacks), 本地方法栈(Native Method Stacks) 和程序计数器(Program Counter Register) 都是每个线程独有的数据区。- 堆(
Heap) 和 方法区(Method Area) 是所有线程共享的数据区。
- 程序计数器(
Program Counter Register)- 指向当前线程虚拟机栈中正在执行方法的字节码指令地址。也就是记录当前线程运行到那里了。
- 但是如果当前运行的方法是本地方法栈中的,即
native方法,那么这个值就是undefined。
- 虚拟机栈(
Java Virtual Machine Stacks)- 就是一个栈的数据机构,里面储存的数据叫做栈帧(
Frame)。 - 每一个栈帧其实表示一个
java方法(native方法除外),也就是说方法调用就是栈帧在虚拟机栈入栈和出栈的过程。
- 就是一个栈的数据机构,里面储存的数据叫做栈帧(
- 本地方法栈(
Native Method Stacks)- 用来储存
native方法调用信息的。
- 用来储存
- 堆(
Heap)- 所有引用类型的数据都是存放在这里,被所有线程共享。
- 用
GC来实现内存自动管理。
- 方法区(
Method Area)- 储存了每个类的结构信息。
- 包括运行期常量池(
Run-Time Constant Pool),字段(field)和方法(method)数据。
1.3 虚拟机栈的栈帧
栈帧是本篇文章最重要的部分,不了解它,你就不知道指令集的操作流程。
栈帧用于存储数据和部分过程结果,以及执行动态链接、方法返回值和异常分派。
- 栈帧随着方法调用而创建,随着方法结束而被摧毁(无论方法是正常调用完成,还是异常调用完成)。
- 每个栈帧都有自己的局部变量表(
local variables),操作数栈(operand stack) 和当前栈帧代表的方法所属类运行时常量池的引用。- 栈帧的局部变量表和操作数栈的大小在编译期就已经确定,放在方法的
Code属性中。也就是说运行期创建栈帧时,是知道栈帧的大小的。
在当前线程虚拟机栈中,只有正在执行方法的那个栈帧是活跃的。这个栈帧被称为当前栈帧(current frame),这个栈帧对应的方法称为当前方法(current frame),定义这个方法的类称为当前类(current method)。
对局部变量表和操作数栈的各种操作,通常都指的是对前栈帧的操作。
1.3.1 局部变量表(local variables)
每个栈帧都包括一个储存局部变量的数组,称为局部变量表。
一共有多少个局部变量,在编译期时就可以知道,将这个值存在方法的
Code属性max_stack变量中。
虽然我们在编译期有多少个局部变量,但是我们也要知道每个局部变量的大小,才能计算局部变量表需要多大空间啊。
在前面介绍的JVM 数据类型时,它们的大小都不一样,该怎么办?
其实是这样的,JVM 虚拟机规范中:
- 一个局部变量的容量可以储存
boolean,byte,char,short,int,float,reference和returnAddress这八种数据类型。- 一个局部变量的容量也称为一个变量槽,槽是局部变量表的最小单位,上面这八种数据类型都可以使用一个槽来存储。
- 在这里你就也明白了,在栈帧中
boolean,byte,char,short这四个类型也是用一个槽存储,相当于使用一个int类型操作。
- 局部变量表可以通过索引来定位访问
索引从
0开始,一个局部变量就占据一个索引。 - 两个局部变量的容量存储
long和double类型。- 这两个类型比较特殊,必须使用连续的两个局部变量(槽)来存储;也就是说占据两个索引,使用较小的索引值来定位。
- 例如一个
long的类型数据储存在索引值为n的局部变量中,实际上n和n + 1一起用来储存long的值;不能单独读取n + 1索引的值;可以在n + 1索引处写入值,但是写入之后,索引n表示的long数据就失效了。
局部变量表中储存那些数据呢?
- 第一种是方法的参数,会将方法定义的参数依次存储到局部变量表中。
- 但是需要注意,如果是实例方法,第一个局部变量(即索引为
0)一定用来存储该实例方法所属对象的引用(就是java中的this),然后再将方法参数依次从索引为1的位置开始存储。 - 如果是静态方法(
static方法),那么就将方法参数依次从索引为0的位置开始存储。
- 但是需要注意,如果是实例方法,第一个局部变量(即索引为
- 第二种就是方法中定义的局部变量。
1.3.2 操作数栈(operand stack)
操作数栈是一个栈的数据结构,配合JVM 指令来进行程序运算的,因为 JVM 就是一个基于栈的虚拟机。
JVM的指令是先将数据复制到操作数栈中,然后根据不同的字节码,进行运算的。- 例如
iadd字节码指令,将两个int类型数据进行相加。就是首先将两个int类型数据放入操作数栈顶,然后执行iadd字节码指令,将两个int类型数据出栈,进行相加,再将结果值入栈。
运行时栈帧操作数栈最大深度其实也在编译期就可以知道了,也是存放在方法 Code 属性的max_locals 中。
最大深度其实就是这个方法运行期间,操作数栈最多的局部变量数。
1.3.3 动态链接
指向栈帧代表的方法所属类运行时常量池的引用,这样在方法中调用其他方法或者访问成员变量时,就可以通过常量池中对应引用找到真实的引用。
二. 指令
在 JVM 中,一条指令是由一个要完成操作的操作码(字节码) 和零个或者多个的操作数来组成的。
JVM使用一个字节表示操作码,也就是说最多拥有256个操作码。- 操作需要的数据,由操作码后面的操作数和操作数栈中数据组成,具体看各个操作码指令的定义。
在 JVM 中的指令分为 Constants,Loads,Stores,Stack,Math,Conversions,Comparisons,References,Control,Extended 和 Reserved 这 11 个种类,下面我们将一一介绍。
Reserved属于JVM保留指令,分别是(0xca) breakpoint,(0xfe) impdep1和(0xff) impdep2。
三. Constants 类型指令
Constants 类型指令都是将常量数据存入操作数栈中。
| 操作码 | 助记符 | 作用 |
|---|---|---|
| 0 (0x0) | nop | 什么也不做 |
| 1 (0x1) | aconst_null | 将常量 null 存入操作数栈 |
| 2 (0x2) | iconst_m1 | 将int类型常量 -1 放入操作数栈中 |
| 3 (0x3) | iconst_0 | 将int类型常量 0 放入操作数栈中 |
| 4 (0x4) | iconst_1 | 将int类型常量 1 放入操作数栈中 |
| 5 (0x5) | iconst_2 | 将int类型常量 2 放入操作数栈中 |
| 6 (0x6) | iconst_3 | 将int类型常量 3 放入操作数栈中 |
| 7 (0x7) | iconst_4 | 将int类型常量 4 放入操作数栈中 |
| 8 (0x8) | iconst_5 | 将int类型常量 5 放入操作数栈中 |
| 9 (0x9) | lconst_0 | 将long类型常量 0 放入操作数栈中 |
| 10 (0xa) | lconst_1 | 将long类型常量 1 放入操作数栈中 |
| 11 (0xb) | fconst_0 | 将float类型常量 0.0 放入操作数栈中 |
| 12 (0xc) | fconst_1 | 将float类型常量 1.0 放入操作数栈中 |
| 13 (0xd) | fconst_2 | 将float类型常量 2.0 放入操作数栈中 |
| 14 (0xe) | dconst_0 | 将double类型常量 0.0 放入操作数栈中 |
| 15 (0xf) | dconst_1 | 将double类型常量 1.0 放入操作数栈中 |
| 16 (0x10) | bipush | 将一个字节有符号int类型常量放入操作数栈中 |
| 17 (0x11) | sipush | 将两个字节有符号int类型常量放入操作数栈中 |
| 18 (0x12) | ldc | 通过一个字节无符号索引将常量池中的 int,float,reference类型常量放入操作数栈中 |
| 19 (0x13) | ldc_w | 通过两个字节无符号索引将常量池中的 int,float,reference类型常量放入操作数栈中 |
| 20 (0x14) | ldc2_w | 通过两个字节无符号索引将常量池中的 long 和 double 类型常量放入操作数栈中 |
3.1 nop
- 指令数据
0 (0x0)(nop) - 操作数栈变化
没有任何变化 - 作用:
nop指令不做任何事情。
3.2 aconst_null
- 指令数据
1 (0x1)(aconst_null) - 操作数栈变化
... → ..., null - 作用: 将常量
null存入操作数栈。
3.3 iconst_<i>
- 指令数据
iconst_<i> - 操作数栈变化
... → ..., <i> - 包括指令
iconst_m1 = 2 (0x2) iconst_0 = 3 (0x3) iconst_1 = 4 (0x4) iconst_2 = 5 (0x5) iconst_3 = 6 (0x6) iconst_4 = 7 (0x7) iconst_5 = 8 (0x8) - 作用: 将
int类型常量-1,0,1,2,3,4和5放入操作数栈中。- 为这几个
int常量设置专门的操作码,这样就不用使用操作数,也减少字节码大小。 - 也可以使用
bipush操作码来实现,但是会增加操作数。
- 为这几个
- 例子
查看字节码public void test() { int i = -1; i = 1; i = 2; i = 3; i = 4; i = 5; i = 6; }0 iconst_m1 1 istore_1 2 iconst_1 3 istore_1 4 iconst_2 5 istore_1 6 iconst_3 7 istore_1 8 iconst_4 9 istore_1 10 iconst_5 11 istore_1 12 bipush 6 14 istore_1 15 return-
istore的指令后面再介绍,你会发现等到6之后,就使用了bipush 6指令了。 - 而且你会发现使用
iconst_<i>指令时,字节码地址每次都是增加一,而使用bipush 6指令,字节码地址增加了二,从12变成了14。
-
3.4 lconst_<l>
- 指令数据
lconst_<l> - 操作数栈变化
... → ..., <l> - 包括指令
lconst_0 = 9 (0x9) lconst_1 = 10 (0xa) - 作用: 将
long类型常量0和1放入操作数栈中。它的意义和
iconst_<i>一样。
3.5 fconst_<f>
- 指令数据
fconst_<f> - 操作数栈变化
... → ..., <f> - 包括指令
fconst_0 = 11 (0xb) fconst_1 = 12 (0xc) fconst_2 = 13 (0xd) - 作用: 将
float类型常量0.0,1.0和2.0放入操作数栈中。它的意义和
iconst_<i>一样。
3.6 dconst_<d>
- 指令数据
dconst_<d> - 操作数栈变化
... → ..., <d> - 包括指令
dconst_0 = 14 (0xe) dconst_1 = 15 (0xf) - 作用: 将
double类型常量0.0和1.0放入操作数栈中。它的意义和
iconst_<i>一样。
3.7 bipush
- 指令数据
16 (0x10)(bipush)byte - 操作数栈变化
... → ..., value - 作用: 将一个字节二进制数带符号扩展成一个
int类型值value,放入操作数栈中。因为
byte是带符号的一个字节二进制数,范围就是-128 -> 127。
3.7 sipush
- 指令数据
17 (0x11)(sipush)byte1byte2 - 操作数栈变化
... → ..., value - 作用: 将两个字节二进制数带符号扩展成一个
int类型值value,放入操作数栈中。因为
byte是有符号的二个字节二进制数,范围就是-32768 -> 32767。
3.8 ldc
- 指令数据
18 (0x12)(ldc)index - 操作数栈变化
... → ..., value - 作用:通过索引将常量池中的
int,float,reference类型常量放入操作数栈中。-
index表示一个无符号二进制数,表示一个常量池索引。 - 这个索引对应的常量应该是
int,float和reference类型。
-
3.9 ldc_w
- 指令数据
19 (0x13)(ldc_w)indexbyte1indexbyte2 - 操作数栈变化
... → ..., value - 作用:通过索引将常量池中的
int,float,reference类型常量放入操作数栈中。- 这个指令与
ldc作用是相同的,只不过这个指令是获取两个无符号二进制数索引对应的常量。 - 在
class字节码文件中,一个class类最多拥有65536个常量,也就是两个无符号二进制数的最大值。
- 这个指令与
3.10 ldc2_w
- 指令数据
20 (0x14)(ldc2_w)indexbyte1indexbyte2 - 操作数栈变化
... → ..., value - 作用:通过索引将常量池中的
long和double类型常量放入操作数栈中。- 这个指令与
ldc_w指令差不多,只不过它获取的是long和double类型常量。
- 这个指令与
3.11 小结
针对五种类型的常量操作
- 引用类型
reference
- 通过
aconst_null指令将null入栈。- 通过
ldc和ldc_w从常量池中获取reference常量入栈。
-
int类型
- 通过
iconst_<i>,bipush和sipush将-32768 --> 32767范围内的整数入栈。也就是说这个范围内的整数是不用放入常量池的。- 通过
ldc和ldc_w从常量池中获取int类型常量入栈。
-
float类型
- 通过
fconst_0,fconst_1,fconst_2将0.0,1.0和2.0这三个float类型数据入栈。也就是说除了这三个float类型数据外,其他的float类型数据都会放在常量池中。- 通过
ldc和ldc_w从常量池中获取float类型常量入栈。
-
long类型
- 通过
lconst_0,lconst_1将0和1这两个long类型数据入栈。也就是说除了这两个long类型数据外,其他的long类型数据都会放在常量池中。- 通过
ldc2_w从常量池中获取long类型常量入栈。
-
double类型
- 通过
dconst_0,dconst_1将0.0和1.0这两个double类型数据入栈。也就是说除了这两个double类型数据外,其他的double类型数据都会放在常量池中。- 通过
ldc2_w从常量池中获取double类型常量入栈。
3.12 实例
public void test() {
Object ob = null;
int i = -1;
i = 1;
i = 2;
i = 3;
i = 4;
i = 5;
i = 127;
i = 129;
long l = 0;
l = 1;
l = 2;
float f = 0;
f = 1;
f = 2;
f = 3;
double d = 0;
d = 1;
d = 2;
}
对应的方法字节码
0 aconst_null
1 astore_1
2 iconst_m1
3 istore_2
4 iconst_1
5 istore_2
6 iconst_2
7 istore_2
8 iconst_3
9 istore_2
10 iconst_4
11 istore_2
12 iconst_5
13 istore_2
14 bipush 127
16 istore_2
17 sipush 129
20 istore_2
21 lconst_0
22 lstore_3
23 lconst_1
24 lstore_3
25 ldc2_w #6 <2>
28 lstore_3
29 fconst_0
30 fstore 5
32 fconst_1
33 fstore 5
35 fconst_2
36 fstore 5
38 ldc #8 <3.0>
40 fstore 5
42 dconst_0
43 dstore 6
45 dconst_1
46 dstore 6
48 ldc2_w #9 <2.0>
51 dstore 6
53 return
四. Loads 类型指令
Loads 类型指令包括从局部变量表(local variables) 中加载数据到操作数栈中,以及从数组中加载数据到操作数栈。
从局部变量表中加载数据:
- 指令数据
或者<i,l,f,d,a>_loadindex<i,l,f,d,a>_load_<n> - 操作数栈变化
... → ..., value
- 通过索引从局部变量表加载数据到操作数栈中。
- 操作数栈栈顶会多一个数据。
从数组中加载数据:
- 指令数据
<i,l,f,d,a,b,c,s>_aload - 操作数栈变化
..., arrayref, index → ..., value
- 通过数组引用
arrayref获取数组下标index数据,放入操作数栈中。- 操作数栈栈顶会多一个数据。
| 操作码 | 助记符 | 作用 |
|---|---|---|
| 21 (0x15) | iload | 通过索引从局部变量表加载int 类型数据到操作数栈中 |
| 22 (0x16) | lload | 通过索引从局部变量表加载long 类型数据到操作数栈中 |
| 23 (0x17) | fload | 通过索引从局部变量表加载float 类型数据到操作数栈中 |
| 24 (0x18) | dload | 通过索引从局部变量表加载double 类型数据到操作数栈中 |
| 25 (0x19) | aload | 通过索引从局部变量表加载double 类型数据到操作数栈中 |
| 26 (0x1a) -> 29 (0x1d) | iload_<n> | 通过索引<n>从局部变量表加载int 类型数据到操作数栈中 |
| 30 (0x1e) -> 33 (0x21) | lload_<n> | 通过索引<n>从局部变量表加载long 类型数据到操作数栈中 |
| 34 (0x22) -> 37 (0x25) | fload_<n> | 通过索引<n>从局部变量表加载float 类型数据到操作数栈中 |
| 38 (0x26) -> 41 (0x29) | dload_<n> | 通过索引<n>从局部变量表加载double 类型数据到操作数栈中 |
| 42 (0x2a) -> 45 (0x2d) | aload_<n> | 通过索引<n>从局部变量表加载reference 类型数据到操作数栈中 |
| 46 (0x2e) | iaload | 根据下标从int类型数组中加载数据到操作数栈中 |
| 47 (0x2f) | laload | 根据下标从long类型数组中加载数据到操作数栈中 |
| 48 (0x30) | faload | 根据下标从float类型数组中加载数据到操作数栈中 |
| 49 (0x31) | daload | 根据下标从double类型数组中加载数据到操作数栈中 |
| 50 (0x32) | aaload | 根据下标从reference类型数组中加载数据到操作数栈中 |
| 51 (0x33) | baload | 根据下标从byte或 boolean类型数组中加载数据到操作数栈中 |
| 52 (0x34) | caload | 根据下标从char类型数组中加载数据到操作数栈中 |
| 53 (0x35) | saload | 根据下标从short类型数组中加载数据到操作数栈中 |
4.1 iload
- 指令数据
21 (0x15)(iload)index - 操作数栈变化
... → ..., value - 作用: 通过一个字节无符号索引
index从局部变量表中获取int类型数据value,放入操作数栈中。
4.2 lload
- 指令数据
22 (0x16)(lload)index - 操作数栈变化
... → ..., value - 作用: 通过一个字节无符号索引
index从局部变量表中获取long类型数据value,放入操作数栈中。因为一个
long类型在局部变量表中占据两个局部变量,其实是通过index和index + 1这两处局部变量数值拼成一个long类型数据。
4.3 fload
- 指令数据
23 ((0x17)(fload)index - 操作数栈变化
... → ..., value - 作用: 通过一个字节无符号索引
index从局部变量表中获取float类型数据value,放入操作数栈中。
4.4 dload
- 指令数据
24 (0x18)(dload)index - 操作数栈变化
... → ..., value - 作用: 通过一个字节无符号索引
index从局部变量表中获取double类型数据value,放入操作数栈中。
4.5 aload
- 指令数据
25 (0x19)(aload)index - 操作数栈变化
... → ..., objectref - 作用: 通过一个字节无符号索引
index从局部变量表中获取reference类型数据objectref,放入操作数栈中。
4.6 iload_<n>
- 指令数据
iload_<n> - 操作数栈变化
... → ..., value - 包括指令
iload_0 = 26 (0x1a) iload_1 = 27 (0x1b) iload_2 = 28 (0x1c) iload_3 = 29 (0x1d) - 作用: 通过索引
<n>(包括0,1,2,3)从局部变量表中获取int类型数据value,放入操作数栈中。
4.7 lload_<n>
- 指令数据
lload_<n> - 操作数栈变化
... → ..., value - 包括指令
lload_0 = 30 (0x1e) lload_1 = 31 (0x1f) lload_2 = 32 (0x20) lload_3 = 33 (0x21) - 作用: 通过索引
<n>(包括0,1,2,3)从局部变量表中获取long类型数据value,放入操作数栈中。
4.8 fload_<n>
- 指令数据
fload_<n> - 操作数栈变化
... → ..., value - 包括指令
fload_0 = 34 (0x22) fload_1 = 35 (0x23) fload_2 = 36 (0x24) fload_3 = 37 (0x25) - 作用: 通过索引
<n>(包括0,1,2,3)从局部变量表中获取float类型数据value,放入操作数栈中。
4.9 dload_<n>
- 指令数据
dload_<n> - 操作数栈变化
... → ..., value - 包括指令
dload_0 = 38 (0x26) dload_1 = 39 (0x27) dload_2 = 40 (0x28) dload_3 = 41 (0x29) - 作用: 通过索引
<n>(包括0,1,2,3)从局部变量表中获取double类型数据value,放入操作数栈中。
4.10 aload_<n>
- 指令数据
aload_<n> - 操作数栈变化
... → ..., objectref - 包括指令
aload_0 = 42 (0x2a) aload_1 = 43 (0x2b) aload_2 = 44 (0x2c) aload_3 = 45 (0x2d) - 作用: 通过索引
<n>(包括0,1,2,3)从局部变量表中获取reference类型数据value,放入操作数栈中。
4.11 iaload
- 指令数据
46 (0x2e)(iaload) - 操作数栈变化
..., arrayref, index → ..., value - 作用: 从
int类型数组arrayref中,根据下标index, 获取int类型数据value,放入操作数栈中。index必须是int类型,数组的下标只能是int类型。
4.12 laload
- 指令数据
47 (0x2f)(laload) - 操作数栈变化
..., arrayref, index → ..., value - 作用: 从
long类型数组arrayref中,根据下标index, 获取long类型数据value,放入操作数栈中。
4.13 faload
- 指令数据
48 (0x30)(faload) - 操作数栈变化
..., arrayref, index → ..., value - 作用: 从
float类型数组arrayref中,根据下标index, 获取float类型数据value,放入操作数栈中。
4.14 daload
- 指令数据
49 (0x31)(daload) - 操作数栈变化
..., arrayref, index → ..., value - 作用: 从
double类型数组arrayref中,根据下标index, 获取double类型数据value,放入操作数栈中。
4.15 aaload
- 指令数据
50 (0x32)(aaload) - 操作数栈变化
..., arrayref, index → ..., value - 作用: 从
reference类型数组arrayref中,根据下标index, 获取reference类型数据value,放入操作数栈中。
4.15 baload
- 指令数据
51 (0x33)(baload) - 操作数栈变化
..., arrayref, index → ..., value - 作用: 从
byte或boolean类型数组arrayref中,根据下标index, 获取byte或boolean类型数据value,放入操作数栈中。
4.16 caload
- 指令数据
52 (0x34)(caload) - 操作数栈变化
..., arrayref, index → ..., value - 作用: 从
char类型数组arrayref中,根据下标index, 获取char类型数据value,放入操作数栈中。
4.17 saload
- 指令数据
53 (0x35)(saload) - 操作数栈变化
..., arrayref, index → ..., value - 作用: 从
short类型数组arrayref中,根据下标index, 获取short类型数据value,放入操作数栈中。
4.18 小结
Loads 类型指令分为两类:
- 从局部变量表(
local variables)中加载数据。- 这类数据只有
int,long,float,double和reference数据类型。 - 根据不同类型,助记符开始符号不一样;比如
i开头的和int有关,a开头的和reference有关。
- 这类数据只有
- 从数组中加载数据。
- 首先这个数组引用,先通过
aload指令从局部变量表中加载到操作数栈中,或者通过ldc和ldc_w从常量池中加载到操作数栈。 - 数组是有八种类型的,除了上面五种还包括
byte,char和short。这个和局部变量表中数据不一样,因为数组的数据是放在堆中的,允许创建这三种类型的数组。
- 首先这个数组引用,先通过
Loads 类型指令执行之后,操作数栈栈顶会多一个数据value。
五. Stores 类型指令
Stores 类型指令包括将操作数栈栈顶数据保存到局部变量表(local variables) 中,或者保存到数组对应下标位置中。
Stores类型指令和Loads类型指令是一一对应的。Loads类型指令是将局部变量表或者数组中的数据加载到操作数栈栈顶;执行之后操作数栈栈顶多一个数据value。- 而
Stores类型指令是将操作数栈栈顶数据出栈,保存到局部变量表或者数组对应下标位置中;执行之后操作数栈会减少一个数据。
保存到局部变量表:
- 指令数据
或者<i,l,f,d,a>_storeindex<i,l,f,d,a>_store_<n> - 操作数栈变化
..., value → ...栈顶元素出栈,保存到局部变量表索引
index处。 - 保存到数组
<i,l,f,d,a,b,c,s>_astore ..., arrayref, index, value → ...- 保存到数组的话,需要数组引用
arrayref,数组下标index,和要保存的数据value。 - 将操作数栈中栈顶三个元素出栈。
- 保存到数组的话,需要数组引用
| 操作码 | 助记符 | 作用 |
|---|---|---|
| 54 (0x36) | istore | 将操作数栈栈顶int类型数据保存到局部变量表对应索引处 |
| 55 (0x37) | lstore | 将操作数栈栈顶long类型数据保存到局部变量表对应索引处 |
| 56 (0x38) | fstore | 将操作数栈栈顶float类型数据保存到局部变量表对应索引处 |
| 57 (0x39) | dstore | 将操作数栈栈顶double类型数据保存到局部变量表对应索引处 |
| 58 (0x3a) | astore | 将操作数栈栈顶reference类型数据保存到局部变量表对应索引处 |
| 59 (0x3b) -> 62 (0x3e) | istore_<n> | 将操作数栈栈顶int类型数据保存到局部变量表索引<n>处 |
| 63 (0x3f) -> 66 (0x42) | lstore_<n> | 将操作数栈栈顶long类型数据保存到局部变量表索引<n>处 |
| 67 (0x43) -> 70 (0x46) | fstore_<n> | 将操作数栈栈顶float类型数据保存到局部变量表索引<n>处 |
| 71 (0x47) -> 74 (0x4a) | dstore_<n> | 将操作数栈栈顶double类型数据保存到局部变量表索引<n>处 |
| 75 (0x4b) -> 78 (0x4e) | astore_<n> | 将操作数栈栈顶reference类型数据保存到局部变量表索引<n>处 |
| 79 (0x4f) | iastore | 将操作数栈栈顶int类型数据保存到数组对应下标位置中 |
| 80 (0x50) | lastore | 将操作数栈栈顶long类型数据保存到数组对应下标位置中 |
| 81 (0x51) | fastore | 将操作数栈栈顶float类型数据保存到数组对应下标位置中 |
| 82 (0x52) | dastore | 将操作数栈栈顶double类型数据保存到数组对应下标位置中 |
| 83 (0x53) | aastore | 将操作数栈栈顶reference类型数据保存到数组对应下标位置中 |
| 84 (0x54) | bastore | 将操作数栈栈顶byte 和 boolean类型数据保存到数组对应下标位置中 |
| 85 (0x55) | castore | 将操作数栈栈顶char类型数据保存到数组对应下标位置中 |
| 86 (0x56) | sastore | 将操作数栈栈顶short类型数据保存到数组对应下标位置中 |
例子
public void test() {
int i1 = 0;
int i2 = i1;
long l1 = 0;
long l2 = l1;
float f1 = 0;
float f2 = f1;
double d1 = 0;
double d2 = d1;
Object obj1 = null;
Object obj2 = obj1;
int[] ints = new int[10];
ints[0] = 0;
ints[1] = ints[0];
boolean[] booleans = new boolean[10];
booleans[0] = false;
booleans[1] = booleans[0];
}
对应字节码
0 iconst_0
1 istore_1
2 iload_1
3 istore_2
4 lconst_0
5 lstore_3
6 lload_3
7 lstore 5
9 fconst_0
10 fstore 7
12 fload 7
14 fstore 8
16 dconst_0
17 dstore 9
19 dload 9
21 dstore 11
23 aconst_null
24 astore 13
26 aload 13
28 astore 14
30 bipush 10
32 newarray 10 (int)
34 astore 15
36 aload 15
38 iconst_0
39 iconst_0
40 iastore
41 aload 15
43 iconst_1
44 aload 15
46 iconst_0
47 iaload
48 iastore
49 bipush 10
51 newarray 4 (boolean)
53 astore 16
55 aload 16
57 iconst_0
58 iconst_0
59 bastore
60 aload 16
62 iconst_1
63 aload 16
65 iconst_0
66 baload
67 bastore
68 return
仔细分析字节码,你会看到发现:
int i1 = 0对应的就是0 iconst_0和istore_1,将0保存到局部变量表1索引处(因为这是一个实例方法,索引0处是当前方法对应的实例引用this)。int i2 = i1对应的就是iload_1和istore_2,先将局部变量表1索引处出数据加载到内存,然后再保存到局部变量表2索引处。long l2 = l1对应的是lload_3和lstore 5。为什么会变成5,那是因为long类型占据两个局部变量。- 最后讲解
booleans[0] = false,对应的是aload 16,iconst_0,iconst_0和bastore。先加载数组引用,第一个iconst_0表示数组下标,第二个iconst_0就表示boolean类型的false,最后通过bastore指令将数据保存到数组中。
六. Stack 类型指令
Stack 类型指令都直接对操作数栈进行操作。
使用
Stack类型指令时,一定要知道此时操作数栈中数据的种类。
我们将操作数栈中数据分为两个种类:
- 第一种:包括
int,float和reference,它们的特点是只使用一个槽的大小就可以存储。 - 第一种:包括
long和double,它们的特点是必须使用两个槽的大小来存储。
| 操作码 | 助记符 | 作用 |
|---|---|---|
| 87 (0x57) | pop | 将栈顶一个槽的数据出栈 |
| 88 (0x58) | pop2 | 将栈顶两个槽的数据出栈 |
| 89 (0x59) | dup | 复制栈顶一个槽的数据,并插入栈顶 |
| 90 (0x5a) | dup_x1 | 复制栈顶一个槽的数据,并插入栈顶以下两个槽之后 |
| 91 (0x5b) | dup_x2 | 复制栈顶一个槽的数据,并插入栈顶以下三个槽之后 |
| 92 (0x5c) (dup2) | dup2 | 复制栈顶两个槽的数据,并插入栈顶 |
| 93 (0x5d) | dup2_x1 | 复制栈顶一个槽的数据,并插入栈顶以下三个槽之后 |
| 94 (0x5e) | dup2_x2 | 复制栈顶一个槽的数据,并插入栈顶以下四个槽之后 |
6.1 pop
- 指令数据
87 (0x57)(pop) - 操作数栈变化
..., value → ... - 作用: 将栈顶的数据
value出栈,这个value必须是第一种类型。
6.2 pop2
- 指令数据
88 (0x58)(pop2) - 操作数栈变化
第一种情况: ..., value2, value1 → ... 第二种情况: ..., value → ... - 作用: 将栈顶的数据出栈,分为两种情况:
- 栈顶有两个第一种类型的数据,直接将这两个数据
value2和value1全部出栈。 - 栈顶是第二种类型的数据,直接将这个数据
value出栈。 - 不允许出现栈顶是第一种类型数据,而下面是第二种类型的数据;如果有这种情况,先用
pop指令将栈顶数据出栈。
- 栈顶有两个第一种类型的数据,直接将这两个数据
6.3 dup
- 指令数据
89 (0x59)(dup) - 操作数栈变化
..., value → ..., value, value - 作用: 复制栈顶数据
value,并插入栈顶;这个value必须是第一种类型。
6.4 dup_x1
- 指令数据
90 (0x5a)(dup_x1) - 操作数栈变化
..., value2, value1 → ..., value1, value2, value1 - 作用: 复制栈顶数据
value1,并插入栈顶以下两个值之后;value1和value2都必须是第一种类型。
6.5 dup_x2
- 指令数据
91 (0x5b)(dup_x2) - 操作数栈变化
第一种情况: ..., value3, value2, value1 → ..., value1, value3, value2, value1 第二种情况: ..., value2, value1 → ..., value1, value2, value1 - 作用: 复制栈顶数据
value1,并插入栈顶以下两个值或三个值之后。- 第一种情况:
value1,value2和value3都必须是第一种类型; 复制后的数据插入栈顶以下三个值之后。 - 第二种情况:
value1是第一种类型,value2是第二种类型;复制后的数据插入栈顶以下两个值之后。 -
dup,dup_x1和dup_x2都是复制第一种类型数据的。
- 第一种情况:
6.6 dup2
- 指令数据
92 (0x5c)(dup2) - 操作数栈变化
第一种情况: ..., value2, value1 → ..., value2, value1, value2, value1 第二种情况: ..., value → ..., value, value - 作用:
- 第一种情况:
value1和value2必须都是第一种类型,复制这两个数据并入栈。 - 第二种情况:
value必须是第二种类型,复制这个数据并入栈。
- 第一种情况:
6.7 dup2_x1
- 指令数据
93 (0x5d)(dup2_x1) - 操作数栈变化
第一种情况: ..., value3, value2, value1 → ..., value2, value1, value3, value2, value1 第二种情况: ..., value2, value1 → ..., value1, value2, value1 - 作用:
- 第一种情况:
value1,value2和value3必须都是第一种类型,复制前两个数据value1和value2插入到value3下面。 - 第二种情况:
value1必须是第二种类型,value2必须是第一种类型,复制栈顶数据value1插入到value2下面。
- 第一种情况:
6.8 dup2_x2
- 指令数据
94 (0x5e)(dup2_x2) - 操作数栈变化
第一种情况: ..., value4, value3, value2, value1 → ..., value2, value1, value4, value3, value2, value1 第二种情况: ..., value3, value2, value1 → ..., value1, value3, value2, value1 第三种情况: ..., value3, value2, value1 → ..., value2, value1, value3, value2, value1 第四种情况: ..., value2, value1 → ..., value1, value2, value1 - 作用:
- 第一种情况:
value1,value2,value3和value4必须都是第一种类型,复制前两个数据value1和value2插入到value4下面。 - 第二种情况:
value1是第二种类型,value2和value3必须是第一种类型,复制栈顶数据value1插入到value3下面。 - 第二种情况:
value1和value2是第一种类型,value3是第二种类型,复制前两个数据value1和value2插入到value3下面。 - 第四种情况:
value1和value2必须都是第二种类型,复制栈顶数据value1插入到value2下面。
- 第一种情况:
6.9 swap
- 指令数据
95 (0x5f)(swap) - 操作数栈变化
..., value2, value1 → ..., value1, value2 ... - 作用: 将栈顶的两个数据
value1和value2交换位置;value1和value2必须都是第一种类型,而且JVM没有提供第二种类型数据交换位置的指令。
6.10 小结
Stack 类型指令对操作数栈操作,一共分为三种类型, 出栈,复制和交换;而且为了处理long和double 这样占据两个槽的数据,提供了不同的指令。
七. Math 类型指令
Math 类型指令都是进行算术运算的。
| 操作码 | 助记符 | 作用 |
|---|---|---|
| 96 (0x60) --> 99 (0x63) | <i,l,f,d>_add | 将栈顶两个对应类型数据相加,再将结果值入栈 |
| 100 (0x64) --> 103 (0x67) | <i,l,f,d>_sub | 将栈顶两个对应类型数据相减,再将结果值入栈 |
| 104 (0x68)--> 107 (0x6b) | <i,l,f,d>_mul | 将栈顶两个对应类型数据相乘,再将结果值入栈 |
| 108 (0x6c) --> 111 (0x7f) | <i,l,f,d>_div | 将栈顶两个对应类型数据相除,再将结果值入栈 |
| 112 (0x70) --> 115 (0x73) | <i,l,f,d>_rem | : 将栈顶两个对应类型数据求余,再将结果值入栈 |
| 116 (0x74) --> 119 (0x77) | <i,l,f,d>_neg | 将栈顶对应类型数据进行取负运算(即-value),再将结果值入栈 |
| 120 (0x78)--> 121 (0x79) | <i,l>_ shl | 将值进行左移运算 |
| 122 (0x7a)--> 123 (0x7b) | <i,l>_ shr | 将值进行有符号右移运算 |
| 124 (0x7c)--> 125 (0x7d) | <i,l>_ ushr | 将值进行无符号右移运算 |
| 126 (0x7e) --> 127 (0x7f) | <i,l>_and | 对值进行按位与运算 |
| 128 (0x80) --> 129 (0x81) | <i,l>_or | 对值进行按位或运算 |
| 130 (0x82) --> 131 (0x83) | <i,l>_xor | 对值进行按位异或运算 |
| 132 (0x84) | iinc | 直接将局部变量表对应索引处的值增加值 |
7.1 <i,l,f,d>_add
- 指令数据
<i,l,f,d>_add - 操作数栈变化
..., value1, value2 → ..., result - 包括指令
iadd = 96 (0x60) ladd = 97 (0x61) fadd = 98 (0x62) dadd = 99 (0x63) - 作用: 将栈顶两个对应类型数据相加,再将结果值入栈,结果值还是对应类型。
7.2 <i,l,f,d>_sub
- 指令数据
<i,l,f,d>_sub - 操作数栈变化
..., value1, value2 → ..., result - 包括指令
isub = 100 (0x64) lsub = 101 (0x65) fsub = 102 (0x66) dsub = 103 (0x67) - 作用: 将栈顶两个对应类型数据相减,再将结果值入栈,结果值还是对应类型。
7.3 <i,l,f,d>_mul
- 指令数据
<i,l,f,d>_mul - 操作数栈变化
..., value1, value2 → ..., result - 包括指令
imul= 104 (0x68) lmul = 105 (0x69) fmul = 106 (0x6a) dmul = 107 (0x6b) - 作用: 将栈顶两个对应类型数据相乘,再将结果值入栈,结果值还是对应类型。
7.4 <i,l,f,d>_div
- 指令数据
<i,l,f,d>_div - 操作数栈变化
..., value1, value2 → ..., result - 包括指令
idiv= 108 (0x6c) ldiv = 109 (0x6d) fdiv = 110 (0x6e) ddiv = 111 (0x7f) - 作用: 将栈顶两个对应类型数据相除,再将结果值入栈,结果值还是对应类型。
7.5 <i,l,f,d>_rem
- 指令数据
<i,l,f,d>_rem - 操作数栈变化
..., value1, value2 → ..., result - 包括指令
irem= 112 (0x70) lrem = 113 (0x71) frem = 114 (0x72) drem = 115 (0x73) - 作用: 将栈顶两个对应类型数据求余,再将结果值入栈,结果值还是对应类型。
float和double类型的求余,是会先经过数值集合转换成对应的int和long类型,进行求余,然后再转换为float和double类型。
7.6 <i,l,f,d>_neg
- 指令数据
<i,l,f,d>_neg - 操作数栈变化
..., value → ..., result - 包括指令
ineg= 116 (0x74) lneg= 117 (0x75) fneg= 118 (0x76) dneg= 119 (0x77) - 作用: 将栈顶对应类型数据进行取负运算(即
-value),再将结果值入栈,结果值还是对应类型。
7.7 ishl
- 指令数据
120 (0x78) (ishl) - 操作数栈变化
..., value1, value2 → ..., result - 作用: 将
int类型的值进行左移运算。-
value1和value2必须都是int类型,执行命令时,value1和value2出栈,然后将value1左移s位(s是value2低5位的值),最后将结果值再入栈。 - 因为
int类型是32位数据,进行位移运算,范围就是0 --> 31,正好可以用5个二进制数表示,所以这里的s就是value2低5位的值。
-
7.8 lshl
- 指令数据
121 (0x79) (lshl) - 操作数栈变化
..., value1, value2 → ..., result - 作用: 将
long类型的值进行左移运算。-
value1和value2必须都是long类型,执行命令时,value1和value2出栈,然后将value1左移s位(s是value2低6位的值),最后将结果值再入栈。 - 因为
long类型是64位数据,进行位移运算,范围就是0 --> 63,正好可以用6个二进制数表示,所以这里的s就是value2低6位的值。
-
7.9 ishr
- 指令数据
122 (0x7a) (ishr) - 操作数栈变化
..., value1, value2 → ..., result - 作用: 将
int类型的值进行右移运算。-
value1和value2必须都是int类型,执行命令时,value1和value2出栈,然后将value1右移s位(s是value2低5位的值),最后将结果值再入栈。 - 因为
int类型是32位数据,进行位移运算,范围就是0 --> 31,正好可以用5个二进制数表示,所以这里的s就是value2低5位的值。
-
7.10 lshr
- 指令数据
123 (0x7b) (lshr) - 操作数栈变化
..., value1, value2 → ..., result - 作用: 将
long类型的值进行右移运算。-
value1和value2必须都是long类型,执行命令时,value1和value2出栈,然后将value1右移s位(s是value2低6位的值),最后将结果值再入栈。 - 因为
long类型是64位数据,进行位移运算,范围就是0 --> 63,正好可以用6个二进制数表示,所以这里的s就是value2低6位的值。
-
7.11 <i,l>_ushr
- 指令数据
<i,l>_ushr - 操作数栈变化
..., value1, value2 → ..., result - 包括指令
124 (0x7c) iushr 125 (0x7d) lushr - 作用: 将
int或long类型的值进行无符号右移运算,即用高位补零的方式进行右移。- 我们知道正数的最高位是
0,负数的最高位是1;我们进行右移运算时,原来的最高位会右移一位,那么新的最高位补什么呢? - 如果使用
<i,l>_shr指令,新的最高位补就和原先最高位是一样的;也就是说正数是补零,负数就补1。 - 如果使用
<i,l>_ushr指令,不管是正数还是负数,新的最高位补只会是零。 - 所以你会发现
<i,l>_shr指令,调用很多次后,最后结果值一定是-1;而<i,l>_ushr指令,调用很多次后,最后结果值一定是0。
- 我们知道正数的最高位是
- 例子
运行结果:public void test() { int index = 0; int i = -62; while (index < 33) { System.out.println("index:"+index+" i:"+i+" " +Integer.toBinaryString(i)); i = i >> 1; index++; } System.out.println("==============="); System.out.println("==============="); index = 0; i = -62; while (index < 33) { System.out.println("index:"+index+" i:"+i+" " +Integer.toBinaryString(i)); i = i >>> 1; index++; } }index:0 i:-62 11111111111111111111111111000010 index:1 i:-31 11111111111111111111111111100001 index:2 i:-16 11111111111111111111111111110000 index:3 i:-8 11111111111111111111111111111000 index:4 i:-4 11111111111111111111111111111100 index:5 i:-2 11111111111111111111111111111110 index:6 i:-1 11111111111111111111111111111111 index:7 i:-1 11111111111111111111111111111111 index:8 i:-1 11111111111111111111111111111111 index:9 i:-1 11111111111111111111111111111111 index:10 i:-1 11111111111111111111111111111111 index:11 i:-1 11111111111111111111111111111111 index:12 i:-1 11111111111111111111111111111111 index:13 i:-1 11111111111111111111111111111111 index:14 i:-1 11111111111111111111111111111111 index:15 i:-1 11111111111111111111111111111111 index:16 i:-1 11111111111111111111111111111111 index:17 i:-1 11111111111111111111111111111111 index:18 i:-1 11111111111111111111111111111111 index:19 i:-1 11111111111111111111111111111111 index:20 i:-1 11111111111111111111111111111111 index:21 i:-1 11111111111111111111111111111111 index:22 i:-1 11111111111111111111111111111111 index:23 i:-1 11111111111111111111111111111111 index:24 i:-1 11111111111111111111111111111111 index:25 i:-1 11111111111111111111111111111111 index:26 i:-1 11111111111111111111111111111111 index:27 i:-1 11111111111111111111111111111111 index:28 i:-1 11111111111111111111111111111111 index:29 i:-1 11111111111111111111111111111111 index:30 i:-1 11111111111111111111111111111111 index:31 i:-1 11111111111111111111111111111111 index:32 i:-1 11111111111111111111111111111111 =============== =============== index:0 i:-62 11111111111111111111111111000010 index:1 i:2147483617 1111111111111111111111111100001 index:2 i:1073741808 111111111111111111111111110000 index:3 i:536870904 11111111111111111111111111000 index:4 i:268435452 1111111111111111111111111100 index:5 i:134217726 111111111111111111111111110 index:6 i:67108863 11111111111111111111111111 index:7 i:33554431 1111111111111111111111111 index:8 i:16777215 111111111111111111111111 index:9 i:8388607 11111111111111111111111 index:10 i:4194303 1111111111111111111111 index:11 i:2097151 111111111111111111111 index:12 i:1048575 11111111111111111111 index:13 i:524287 1111111111111111111 index:14 i:262143 111111111111111111 index:15 i:131071 11111111111111111 index:16 i:65535 1111111111111111 index:17 i:32767 111111111111111 index:18 i:16383 11111111111111 index:19 i:8191 1111111111111 index:20 i:4095 111111111111 index:21 i:2047 11111111111 index:22 i:1023 1111111111 index:23 i:511 111111111 index:24 i:255 11111111 index:25 i:127 1111111 index:26 i:63 111111 index:27 i:31 11111 index:28 i:15 1111 index:29 i:7 111 index:30 i:3 11 index:31 i:1 1 index:32 i:0 0
7.12 <i,l>_and
- 指令数据
<i,l>_and - 操作数栈变化
..., value1, value2 → ..., result - 包括指令
126 (0x7e) iand 127 (0x7f) land - 作用: 对
int或者long进行按位与运算。value1和value2必须是同一种类型,结果值也是这种类型。
7.13 <i,l>_or
- 指令数据
<i,l>_or - 操作数栈变化
..., value1, value2 → ..., result - 包括指令
128 (0x80) ior 129 (0x81) lor - 作用: 对
int或者long进行按位或运算。value1和value2必须是同一种类型,结果值也是这种类型。
7.14 <i,l>_xor
- 指令数据
<i,l>_xor - 操作数栈变化
..., value1, value2 → ..., result - 包括指令
130 (0x82) ixor 131 (0x83) lxor - 作用: 对
int或者long进行按位异或运算。- 异或运算就是相同为
0,不同为1;即0 ^ 1结果是1, 而0 ^ 0或者1 ^ 1结果都是0。 - 由于异或运算这个特性,位取反运算(
~)就是通过这个异或-1来实现的。因为-1的二进制表示1111....1都是1,那么它和任何数异或,就相当于取反。
- 异或运算就是相同为
- 例子
字节码:public void test() { int i = 0; // 取反位运算 i = ~i; // 异或位运算 i = i ^ 1; }0 iconst_0 1 istore_1 // 取反位运算 2 iload_1 3 iconst_m1 // 异或 -1 4 ixor 5 istore_1 // 异或位运算 6 iload_1 7 iconst_1 8 ixor 9 istore_1 10 return
7.15 iinc
- 指令数据
132 (0x84) (iinc)index const - 操作数栈变化
不会有任何变化 - 作用: 直接将局部变量表中一个字节无符号索引值
index处的int类型值,增加一个字节有符号数const的大小。- 这个直接对局部变量表中数进行操作,一步操作就搞定;普通加法运算,是要先将局部变量表值加载到操作数栈中,进行加法运算,然后再将结果值存入局部变量表中。
-
const是一个字节的有符号数,范围就是-128 --> 127,也就是说JVM是允许这个范围内的数,使用iinc指令。但是在java只有int类型的++和--运算符能使用这个iinc指令。 - 特别注意,这个是对局部变量表的操作,所以实例中的
int类型属性和类的int类型属性,即使用了++和--运算符,也不是使用这个指令的。
- 例子
字节码:// 静态变量 static int si = 0; // 实例变量 int ci = 0; public void test() { // 局部变量 int i = 0; // 使用 iinc 1 1 i++; // 使用 iinc 1 -1 i--; i = i + 1; // 都不使用 iinc 指令 ci ++; si ++; }iconst_0 1 istore_1 2 iinc 1 by 1 // i++ 5 iinc 1 by -1 // i-- // i = i + 1 开始 8 iload_1 9 iconst_1 10 iadd 11 istore_1 // i = i + 1 结束 // ci ++ 开始 12 aload_0 13 dup 14 getfield #2 <com/zhang/jvm/test/T.ci> 17 iconst_1 18 iadd 19 putfield #2 <com/zhang/jvm/test/T.ci> // ci ++ 结束 // si ++ 开始 22 getstatic #3 <com/zhang/jvm/test/T.si> 25 iconst_1 26 iadd 27 putstatic #3 <com/zhang/jvm/test/T.si> // si ++ 结束 30 return
7.16 小结
大部分 Math 类型指令都是对栈中两个数进行运算,再将结果值入栈
..., value1, value2 →
..., result
只有两个类型指令不一样,那就是 <i,l,f,d>_neg 和 iinc
-
<i,l,f,d>_neg..., value → ..., result -
iinc不会对操作数栈进行任何操作。
八. Conversions 类型指令
Conversions 类型指令是用来执行数值类型转换的。
Conversions 类型指令只有一个操作码,没有操作数;对操作数栈的变化是
..., value →
..., result
数值类型转换分为两种:
- 扩展了位数,例如
int转成long,又32位变成64位。这种都是带符号扩展,也就是说用原来最高位
0或1来填充新添加位的值。 - 缩小了位数,例如
long转成int,又64位变成32位。这种就是直接截断,将多余的位数删掉,只保留缩小后的位数。这个就会导致转换后的数可能正负号不一样了。
转换的规则大体都遵守上面的原则,但是byte,char和short 这三个类型需要注意:
-
byte,char和short都会直接当成int类型使用- 一个字节的
byte类型数据,会带符号扩展成int类型。 - 两个字节的
short类型数据,会带符号扩展成int类型。 - 两个字节的
char类型数据,会不带符号扩展成int类型。
- 一个字节的
- 这个和
java语言中类型自动升级是不一样的- 例如
int i = 10; long l = i,不用写强转符号,但是编译成的字节码中,会使用i2l这个指令。 - 但是
short s = 10; int i = (int)s;, 即使写了强转符号,编译成的字节码中,不会有什么short转int指令,事实上也没有这个指令。
- 例如
- 只有
i2b,i2c和i2s-
i2b截取一个字节,i2c和i2s截取两个字节。 - 又因为在
JVM中,byte,char和short这三个类型都是当int类型使用的,也就是说截取之后,又是当前int存储。 - 这个时候,
byte和short扩展成int类型,是带符号扩展的,而char是不带符号扩展的,因为char肯定是正数或者0。
-
- 其他类型转换成
byte,char和short需要使用两个指令,先转换成
int类型,再转换成这三种类型。
例子
public void test() {
int i = Short.MAX_VALUE + 2;
byte b = (byte) i;
short s = (short) i;
char c = (char) i;
System.out.println(i+" "+Integer.toBinaryString(i));
System.out.println(b+" "+Integer.toBinaryString(b));
System.out.println(s+" "+Integer.toBinaryString(s));
System.out.println((int)c+" "+Integer.toBinaryString(c));
System.out.println("=====================");
i = -12312321;
b = (byte) i;
s = (short) i;
c = (char) i;
System.out.println(i+" "+Integer.toBinaryString(i));
System.out.println(b+" "+Integer.toBinaryString(b));
System.out.println(s+" "+Integer.toBinaryString(s));
System.out.println((int)c+" "+Integer.toBinaryString(c));
}
运算结果
32769 1000000000000001
1 1
-32767 11111111111111111000000000000001
32769 1000000000000001
=====================
-12312321 11111111010001000010000011111111
-1 11111111111111111111111111111111
8447 10000011111111
8447 10000011111111
| 操作码 | 助记符 | 作用 |
|---|---|---|
| 133 (0x85) | i2l | 将 int 类型转换成long 类型 |
| 134 (0x86) | i2f | 将 int 类型转换成float 类型 |
| 135 (0x87) | i2d | 将 int 类型转换成double 类型 |
| 136 (0x88) | l2i | 将 long 类型转换成int 类型 |
| 137 (0x89) | l2f | 将 long 类型转换成float 类型 |
| 138 (0x8a) | l2d | 将 long 类型转换成double 类型 |
| 139 (0x8b) | f2i | 将 float 类型转换成int 类型 |
| 140 (0x8c) | f2l | 将 float 类型转换成long 类型 |
| 141 (0x8d) | f2d | 将 float 类型转换成double 类型 |
| 142 (0x8e) | d2i | 将 double 类型转换成int 类型 |
| 133 (0x8f) | d2l | 将 double 类型转换成long 类型 |
| 144 (0x90) | d2f | 将 double 类型转换成float 类型 |
| 145 (0x91) | i2b | 将 int 类型转换成byte 类型 |
| 146 (0x92) | i2c | 将 int 类型转换成char 类型 |
| 147 (0x93) | i2s | 将 int 类型转换成short 类型 |
九. Comparisons 类型指令
Comparisons 类型指令是用来进行逻辑运算的。
| 操作码 | 助记符 | 作用 |
|---|---|---|
| 148 (0x94) | lcmp | 比较两个 long 类型的大小 |
| 149 (0x95) -> 150 (0x96) | fcmp<op> | 比较两个 float 类型的大小 |
| 151 (0x97) -> 152 (0x98) | dcmp<op> | 比较两个 double 类型的大小 |
| 153 (0x99) -> 158 (0x9e) | if<cond> |
int数据与零比较的条件分支判断 |
| 159 (0x9f) -> 164 (0xa4) | if_icmp<cond> |
int类型比较的条件分支判断 |
| 165 (0xa5) -> 166 (0xa6) | if_acmp<cond> |
reference类型比较的条件分支判断 |
9.1 lcmp
- 指令数据
148 (0x94)(lcmp) - 操作数栈变化
..., value1, value2 → ..., result - 作用: 比较两个
long类型的大小-
value1和value2必须都是long类型。 - 如果
value1大于value2,将int类型数据1放入操作数栈。 - 如果
value1等于value2,将int类型数据0放入操作数栈。 - 如果
value1小于value2,将int类型数据-1放入操作数栈。
-
9.2 fcmp<op>
- 指令数据
fcmp<op> - 操作数栈变化
..., value1, value2 → ..., result - 包括指令
fcmpg = 150 (0x96) fcmpl = 149 (0x95) - 作用:比较两个
float类型的大小-
value1和value2必须都是float类型。先通过数值集合转换得到value1'和value2',开始进行比较: - 如果
value1'大于value2',将int类型数据1放入操作数栈。 - 如果
value1'等于value2',将int类型数据0放入操作数栈。 - 如果
value1'小于value2',将int类型数据-1放入操作数栈。 - 如果
value1'和value2'有一个是NaN,那么fcmpg命令是将int类型数据1放入操作数栈;而fcmpl命令是将int类型数据-1放入操作数栈。
-
9.3 dcmp<op>
- 指令数据
dcmp<op> - 操作数栈变化
..., value1, value2 → ..., result - 包括指令
dcmpg = 152 (0x98) dcmpl = 151 (0x97) - 作用:比较两个
double类型的大小-
value1和value2必须都是double类型。先通过数值集合转换得到value1'和value2',开始进行比较: - 如果
value1'大于value2',将int类型数据1放入操作数栈。 - 如果
value1'等于value2',将int类型数据0放入操作数栈。 - 如果
value1'小于value2',将int类型数据-1放入操作数栈。 - 如果
value1'和value2'有一个是NaN,那么dcmpg命令是将int类型数据1放入操作数栈;而dcmpl命令是将int类型数据-1放入操作数栈。
-
9.3 if<cond>
- 指令数据
dcmp<op>branchbyte1 branchbyte2 - 操作数栈变化
..., value → ... - 包括指令
ifeq = 153 (0x99) ifne = 154 (0x9a) iflt = 155 (0x9b) ifge = 156 (0x9c) ifgt = 157 (0x9d) ifle = 158 (0x9e) - 作用:
int数据与零比较的条件分支判断value必须是int类型,然后与零进行比较:-
ifeqsucceeds if and only ifvalue = 0 -
ifnesucceeds if and only ifvalue ≠ 0 -
ifltsucceeds if and only ifvalue < 0 -
iflesucceeds if and only ifvalue ≤ 0 -
ifgtsucceeds if and only ifvalue > 0 -
ifgesucceeds if and only ifvalue ≥ 0
如果比较结果为真,那么程序就跳转到由branchbyte1和branchbyte2构造的两个字节无符号指定地址执行,如果为假,那么就 继续执行if<cond>指令后面的其他指令。
-
9.4 if_icmp<cond>
- 指令数据
if_icmp<cond>branchbyte1 branchbyte2 - 操作数栈变化
..., value1, value2 → ... - 包括指令
if_icmpeq = 159 (0x9f) if_icmpne = 160 (0xa0) if_icmplt = 161 (0xa1) if_icmpge = 162 (0xa2) if_icmpgt = 163 (0xa3) if_icmple = 164 (0xa4) - 作用:
int类型比较的条件分支判断这个指令与
if<cond>指令的操作逻辑是一样的,if<cond>指令就相当于value2为零的if_icmp<cond>特殊格式。-
if_icmpeqsucceeds if and only ifvalue1 = value2 -
if_icmpnesucceeds if and only ifvalue1 ≠ value2 -
if_icmpltsucceeds if and only ifvalue1 < value2 -
if_icmplesucceeds if and only ifvalue1 ≤ value2 -
if_icmpgtsucceeds if and only ifvalue1 > value2 -
if_icmpgesucceeds if and only ifvalue1 ≥ value2
-
9.5 if_acmp<cond>
- 指令数据
if_acmp<cond>branchbyte1 branchbyte2 - 操作数栈变化
..., value1, value2 → ... - 包括指令
if_acmpeq = 165 (0xa5) if_acmpne = 166 (0xa6) - 作用:
reference类型比较的条件分支判断reference类型只有相等和不等的条件判断,还有是否为null的条件判断,这个在Extended扩展类型指令中。-
if_acmpeqsucceeds if and only ifvalue1 = value2 -
if_acmpnesucceeds if and only ifvalue1 ≠ value2
-
9.6 小结
Comparisons 类型指令还是比较简单的,主要分为以下几类:
-
long,float和double类型
它们的比较指令操作数栈变化如下..., value1, value2 → ..., result-
value1和value2必须是各自的类型。 - 比较结果值
result是int类型,如果大于就是1,等于就是0,小于就是-1。 -
float和double比较特殊,有NaN值需要进行特殊处理。 - 这几个比较指令,都不涉及程序跳转,只是将比较结果值入栈。
-
-
int类型
if<cond>和if_icmp<cond>都表示int类型比较的条件分支判断,只不过if<cond>只与零进行比较。
它们的操作数栈变化
根据比较结果进行程序跳转。..., value1, value2 → ... -
reference类型
它只有相等和不等的条件判断。
9.7 例子
public void test() {
long l = 0;
if (l > 1) {
l ++;
} else {
l --;
}
}
字节码
// long l = 0;
0 lconst_0
1 lstore_1
// l > 1
2 lload_1
3 lconst_1
4 lcmp
5 ifle 15 (+10) // if判断,如果小于等于0,那么就跳转到 `15` 进行 l--
// l ++
8 lload_1
9 lconst_1
10 ladd
11 lstore_1
12 goto 19 (+7) // 跳转到 return ,返回
// l --
15 lload_1
16 lconst_1
17 lsub
18 lstore_1
19 return
十一. Control 类型指令
Control 类型指令都是程序跳转的指令。
| 操作码 | 助记符 | 作用 |
|---|---|---|
| 167 (0xa7) | goto | 无条件分支跳转 |
| 168 (0xa8) | jsr | 程序段落跳转 |
| 169 (0xa9) | ret | 代码片段中返回 |
| 170 (0xaa) | tableswitch | 根据索引值在跳转表中寻找匹配的分支进行跳转 |
| 171 (0xab) | lookupswitch | 根据键值在跳转表中寻找匹配的分支进行跳转 |
| 172 (0xac) -> 176 (0xb0) | <i,l,f,d,a>_return | 从当前方法带返回值返回调用处 |
| 177 (0xb1) | return | 从当前方法直接返回调用处,不携带返回值 |
10.1 goto
- 指令数据
167 (0xa7)(goto)branchbyte1 branchbyte2 - 操作数栈变化
没有任何变化 ... - 作用: 无条件跳转到两个字节
branchbyte1和branchbyte2组成的无符号指令地址处。
10.2 jsr, jsr_w 和 ret
这三个指令已经基本上不使用了。
10.3 tableswitch
- 指令数据
170 (0xaa)(tableswitch)<0-3 byte pad> defaultbyte1 defaultbyte2 defaultbyte3 defaultbyte4 lowbyte1 lowbyte2 lowbyte3 lowbyte4 highbyte1 highbyte2 highbyte3 highbyte4 jump offsets... - 操作数栈变化
..., index → ... ... - 作用: 根据索引值
index在跳转表中寻找匹配的分支进行跳转。
10.4 lookupswitch
- 指令数据
171 (0xab)(lookupswitch)<0-3 byte pad> defaultbyte1 defaultbyte2 defaultbyte3 defaultbyte4 npairs1 npairs2 npairs3 npairs4 match-offset pairs... - 操作数栈变化
..., key → ... ... - 作用: 根据键值
key在跳转表中寻找匹配的分支进行跳转。
10.5 <i,l,f,d,a>_return
- 指令数据
<i,l,f,d,a>_return - 操作数栈变化
..., value → [empty] ... - 包括指令
172 (0xac) ireturn 173 (0xad) lreturn 174 (0xae) freturn 175 (0xaf) dreturn 176 (0xb0) areturn - 作用: 从当前方法带返回值
value返回调用处。
10.6 return
- 指令数据
177 (0xb1)(return) - 操作数栈变化
... → [empty] ... - 作用: 从当前方法直接返回调用处,不携带返回值。
十一. References 类型指令
References 类型指令是对引用类型进行操作的指令集。
| 操作码 | 助记符 | 作用 |
|---|---|---|
| 178 (0xb2) | getstatic | 获取静态属性的值入栈 |
| 179 (0xb3) | putstatic | 将操作数栈栈顶数据存入静态属性中 |
| 180 (0xb4) | getfield | 获取实例属性的值入栈 |
| 181 (0xb5) | putfield | 将操作数栈栈顶数据存入实例属性中 |
| 182 (0xb6) | invokevirtual | 用于调用所有的虚方法(不包括实例构造器,私有方法和父类中的方法) |
| 183 (0xb7) | invokespecial | 用于调用实例构造器<init>()方法、私有方法和父类中的方法 |
| 184 (0xb8) | invokestatic | 用于调用静态方法 |
| 185 (0xb9) | invokeinterface | 用于调用接口方法 |
| 186 (0xba) | invokedynamic | 先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法 |
| 187 (0xbb) | new | 创建一个实例对象并入栈 |
| 188 (0xbc) | newarray | 创建一个原始类型数组实例对象并入栈 |
| 189 (0xbd) | anewarray | 创建一个引用类型数组实例对象并入栈 |
| 190 (0xbe) | arraylength | 获取数组长度并入栈 |
| 191 (0xbf) | athrow | 抛出一个 exception 或者 error |
| 192 (0xc0) | checkcast | 进行引用类型类型转换 |
| 193 (0xc1) | instanceof | 判断对象是不是指定类型 |
11.1 getstatic
- 指令数据
178 (0xb2)(getstatic)indexbyte1 indexbyte2 - 操作数栈变化
..., → ..., value - 作用: 获取静态属性的值入栈。
通过两个字节无符号数(
(indexbyte1 << 8) | indexbyte2)索引从运行时常量池冲获取类的静态属性的引用。
11.2 putstatic
- 指令数据
179 (0xb3)(putstatic)indexbyte1 indexbyte2 - 操作数栈变化
..., value → ... - 作用: 将操作数栈栈顶数据
value存入静态属性中。通过两个字节无符号数(
(indexbyte1 << 8) | indexbyte2)索引从运行时常量池冲获取类的静态属性的引用。
11.3 getfield
- 指令数据
180 (0xb4)(getfield)indexbyte1 indexbyte2 - 操作数栈变化
..., objectref → ..., value - 作用: 获取
objectref的实例属性的值入栈。通过两个字节无符号数(
(indexbyte1 << 8) | indexbyte2)索引从运行时常量池冲获取objectref的实例属性的引用。
11.4 putfield
- 指令数据
181 (0xb5)(putfield)indexbyte1 indexbyte2 - 操作数栈变化
..., objectref, value → ... - 作用: 将操作数栈栈顶数据
value存入objectref对应的实例属性中。通过两个字节无符号数(
(indexbyte1 << 8) | indexbyte2)索引从运行时常量池冲获取objectref的实例属性的引用。
11.5 invokevirtual
- 指令数据
182 (0xb6)(invokevirtual)indexbyte1 indexbyte2 - 操作数栈变化
..., objectref, [arg1, [arg2 ...]] → ... - 作用: 用于调用所有的虚方法(不包括实例构造器,私有方法和父类中的方法)。
11.6 invokespecial
- 指令数据
183 (0xb7)(invokespecial)indexbyte1 indexbyte2 - 操作数栈变化
..., objectref, [arg1, [arg2 ...]] → ... - 作用: 用于调用实例构造器
<init>()方法、私有方法和父类中的方法。
11.7 invokestatic
- 指令数据
184 (0xb8)(invokestatic)indexbyte1 indexbyte2 - 操作数栈变化
..., [arg1, [arg2 ...]] → ... - 作用: 用于调用静态方法。
11.8 invokeinterface
- 指令数据
185 (0xb9)(invokeinterface)indexbyte1 indexbyte2 count 0 - 操作数栈变化
..., objectref, [arg1, [arg2 ...]] → ... - 作用: 用于调用接口方法。
11.9 invokedynamic
- 指令数据
186 (0xba)(invokedynamic)indexbyte1 indexbyte2 0 0 - 操作数栈变化
..., [arg1, [arg2 ...]] → ... - 作用: 先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法。
11.10 new
- 指令数据
187 (0xbb)(new)indexbyte1 indexbyte2 - 操作数栈变化
... → ..., objectref - 作用: 创建一个实例对象并入栈。
11.11 newarray
-
指令数据
188 (0xbc)(newarray)atype -
操作数栈变化
..., count → ..., arrayref -
作用: 创建一个原始类型数组实例对象并入栈。
count表示创建数组的长度,必须是int类型。atype代表数组中存放的原始类型:Array Type atype T_BOOLEAN 4 T_CHAR 5 T_FLOAT 6 T_DOUBLE 7 T_BYTE 8 T_SHORT 9 T_INT 10 T_LONG 11
11.12 anewarray
- 指令数据
189 (0xbd)(anewarray)indexbyte1 indexbyte2 - 操作数栈变化
..., count → ..., arrayref - 作用: 创建一个引用类型数组实例对象并入栈。
11.13 arraylength
- 指令数据
190 (0xbe)(arraylength) - 操作数栈变化
..., arrayref → ..., length - 作用: 获取数组长度并入栈。
11.14 athrow
- 指令数据
191 (0xbf)(athrow) - 操作数栈变化
..., objectref → objectref - 作用: 抛出一个
exception或者error。
11.15 checkcast
- 指令数据
192 (0xc0)(checkcast)indexbyte1 indexbyte2 - 操作数栈变化
..., objectref → ..., objectref - 作用: 进行引用类型类型转换。
11.16 instanceof
- 指令数据
193 (0xc1)(instanceof)indexbyte1 indexbyte2 - 操作数栈变化
..., objectref → ..., result - 作用: 判断对象是不是指定类型。
11.17 monitorenter 和 monitorexit
- 指令数据
194 (0xc2)(monitorenter)195 (0xc3)(monitorexit) - 操作数栈变化
..., objectref → ... - 作用: 进入一个对象的监控(
monitor) 和 退出一个对象的监控(monitor) 。使用这两个命令实现同步锁
synchronized。
十二. Extended 类型指令
Extended 类型指令是扩展指令。
| 操作码 | 助记符 | 作用 |
|---|---|---|
| 196 (0xc4) | wide | 通过附加的字节扩展来扩展局部变量表索引 |
| 197 (0xc5) | multianewarray | 创建多维数组 |
| 198 (0xc6) | ifnull | 判断是否为null 进行条件跳转 |
| 199 (0xc7) | ifnonnull | 判断是否不为null 进行条件跳转 |
| 200 (0xc8) | goto_w | 无条件分支跳转 |
| 201 (0xc9) | jsr_w | 程序段落跳转 |
12.1 wide
这个指令主要是用来针对局部变量表(local variables)索引的。
- 我们知道局部变量表的长度最多时,两个字节无符号数的最大值。
- 但是大部分情况下,局部变量表的长度都是蛮小的,不会超过一个字节无符号数。
- 因此我们之前学到的
<>_load和<>_store从局部变量表中加载和存储数据,操作数都是只有一个字节;还有就是iinc指令,直接在局部变量进行加法操作,也只有一个字节。- 所以当局部变量表超过一个字节无符号数时,这些命令就不行了。就需要使用
wide指令进行扩展了。
-
指令数据
类型一:wide<>_load和<>_storeindexbyte1 indexbyte2 类型二:
wideiincindexbyte1 indexbyte2 constbyte1 constbyte1 -
操作数栈变化
和被扩展的指令一致
12.2 multianewarray
- 指令数据
197 (0xc5)(multianewarray) - 操作数栈变化
..., count1, [count2, ...] → ..., arrayref ... - 作用: 创建多维数组,并入栈。
- 实例
字节码public void test() { int[][][] i3Arr = new int[3][3][3]; }0 iconst_3 1 iconst_3 2 iconst_3 3 multianewarray #3 <[[[I> dim 3 7 astore_1 8 return
12.3 ifnull
- 指令数据
198 (0xc6)(ifnull)branchbyte1 branchbyte2 - 操作数栈变化
..., value → ... - 作用: 根据操作数栈栈顶值
value是否为null进行条件跳转。
12.4 ifnonnull
- 指令数据
199 (0xc7)(ifnonnull)branchbyte1 branchbyte2 - 操作数栈变化
..., value → ... - 作用: 根据操作数栈栈顶值
value是否不是null进行条件跳转。
11.5 goto_w
- 指令数据
200 (0xc8)(goto_w)branchbyte1 branchbyte2 branchbyte3 branchbyte4 - 操作数栈变化
没有任何变化 ... - 作用: 无条件跳转到四个字节组成的无符号指令地址处。