一. 前置内容
本篇文章是对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_
- 指令数据
iconst_
- 操作数栈变化
... → ...,
- 包括指令
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_
指令时,字节码地址每次都是增加一,而使用bipush 6
指令,字节码地址增加了二,从12
变成了14
。
-
3.4 lconst_
- 指令数据
lconst_
- 操作数栈变化
... → ...,
- 包括指令
lconst_0 = 9 (0x9) lconst_1 = 10 (0xa)
- 作用: 将
long
类型常量0
和1
放入操作数栈中。它的意义和
iconst_
一样。
3.5 fconst_
- 指令数据
fconst_
- 操作数栈变化
... → ...,
- 包括指令
fconst_0 = 11 (0xb) fconst_1 = 12 (0xc) fconst_2 = 13 (0xd)
- 作用: 将
float
类型常量0.0
,1.0
和2.0
放入操作数栈中。它的意义和
iconst_
一样。
3.6 dconst_
- 指令数据
dconst_
- 操作数栈变化
... → ...,
- 包括指令
dconst_0 = 14 (0xe) dconst_1 = 15 (0xf)
- 作用: 将
double
类型常量0.0
和1.0
放入操作数栈中。它的意义和
iconst_
一样。
3.7 bipush
- 指令数据
16 (0x10)
(bipush
)byte
- 操作数栈变化
... → ..., value
- 作用: 将一个字节二进制数带符号扩展成一个
int
类型值value
,放入操作数栈中。因为
byte
是带符号的一个字节二进制数,范围就是-128 -> 127
。
3.7 sipush
- 指令数据
17 (0x11)
(sipush
)byte1
byte2
- 操作数栈变化
... → ..., 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
)indexbyte1
indexbyte2
- 操作数栈变化
... → ..., value
- 作用:通过索引将常量池中的
int
,float
,reference
类型常量放入操作数栈中。- 这个指令与
ldc
作用是相同的,只不过这个指令是获取两个无符号二进制数索引对应的常量。 - 在
class
字节码文件中,一个class
类最多拥有65536
个常量,也就是两个无符号二进制数的最大值。
- 这个指令与
3.10 ldc2_w
- 指令数据
20 (0x14)
(ldc2_w
)indexbyte1
indexbyte2
- 操作数栈变化
... → ..., value
- 作用:通过索引将常量池中的
long
和double
类型常量放入操作数栈中。- 这个指令与
ldc_w
指令差不多,只不过它获取的是long
和double
类型常量。
- 这个指令与
3.11 小结
针对五种类型的常量操作
- 引用类型
reference
- 通过
aconst_null
指令将null
入栈。- 通过
ldc
和ldc_w
从常量池中获取reference
常量入栈。
-
int
类型
- 通过
iconst_
,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
) 中加载数据到操作数栈中,以及从数组中加载数据到操作数栈。
从局部变量表中加载数据:
- 指令数据
_load
index
_load_
- 操作数栈变化
... → ..., value
- 通过索引从局部变量表加载数据到操作数栈中。
- 操作数栈栈顶会多一个数据。
从数组中加载数据:
- 指令数据
_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_ |
通过索引 从局部变量表加载int 类型数据到操作数栈中 |
30 (0x1e) -> 33 (0x21) | lload_ |
通过索引 从局部变量表加载long 类型数据到操作数栈中 |
34 (0x22) -> 37 (0x25) | fload_ |
通过索引 从局部变量表加载float 类型数据到操作数栈中 |
38 (0x26) -> 41 (0x29) | dload_ |
通过索引 从局部变量表加载double 类型数据到操作数栈中 |
42 (0x2a) -> 45 (0x2d) | aload_ |
通过索引 从局部变量表加载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_
- 指令数据
iload_
- 操作数栈变化
... → ..., value
- 包括指令
iload_0 = 26 (0x1a) iload_1 = 27 (0x1b) iload_2 = 28 (0x1c) iload_3 = 29 (0x1d)
- 作用: 通过索引
(包括0
,1
,2
,3
)从局部变量表中获取int
类型数据value
,放入操作数栈中。
4.7 lload_
- 指令数据
lload_
- 操作数栈变化
... → ..., value
- 包括指令
lload_0 = 30 (0x1e) lload_1 = 31 (0x1f) lload_2 = 32 (0x20) lload_3 = 33 (0x21)
- 作用: 通过索引
(包括0
,1
,2
,3
)从局部变量表中获取long
类型数据value
,放入操作数栈中。
4.8 fload_
- 指令数据
fload_
- 操作数栈变化
... → ..., value
- 包括指令
fload_0 = 34 (0x22) fload_1 = 35 (0x23) fload_2 = 36 (0x24) fload_3 = 37 (0x25)
- 作用: 通过索引
(包括0
,1
,2
,3
)从局部变量表中获取float
类型数据value
,放入操作数栈中。
4.9 dload_
- 指令数据
dload_
- 操作数栈变化
... → ..., value
- 包括指令
dload_0 = 38 (0x26) dload_1 = 39 (0x27) dload_2 = 40 (0x28) dload_3 = 41 (0x29)
- 作用: 通过索引
(包括0
,1
,2
,3
)从局部变量表中获取double
类型数据value
,放入操作数栈中。
4.10 aload_
- 指令数据
aload_
- 操作数栈变化
... → ..., objectref
- 包括指令
aload_0 = 42 (0x2a) aload_1 = 43 (0x2b) aload_2 = 44 (0x2c) aload_3 = 45 (0x2d)
- 作用: 通过索引
(包括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
类型指令是将操作数栈栈顶数据出栈,保存到局部变量表或者数组对应下标位置中;执行之后操作数栈会减少一个数据。
保存到局部变量表:
- 指令数据
_store
index
_store_
- 操作数栈变化
..., value → ...
栈顶元素出栈,保存到局部变量表索引
index
处。 - 保存到数组
_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_ |
将操作数栈栈顶int 类型数据保存到局部变量表索引 处 |
63 (0x3f) -> 66 (0x42) | lstore_ |
将操作数栈栈顶long 类型数据保存到局部变量表索引 处 |
67 (0x43) -> 70 (0x46) | fstore_ |
将操作数栈栈顶float 类型数据保存到局部变量表索引 处 |
71 (0x47) -> 74 (0x4a) | dstore_ |
将操作数栈栈顶double 类型数据保存到局部变量表索引 处 |
75 (0x4b) -> 78 (0x4e) | astore_ |
将操作数栈栈顶reference 类型数据保存到局部变量表索引 处 |
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) | _add | 将栈顶两个对应类型数据相加,再将结果值入栈 |
100 (0x64) --> 103 (0x67) | _sub | 将栈顶两个对应类型数据相减,再将结果值入栈 |
104 (0x68)--> 107 (0x6b) | _mul | 将栈顶两个对应类型数据相乘,再将结果值入栈 |
108 (0x6c) --> 111 (0x7f) | _div | 将栈顶两个对应类型数据相除,再将结果值入栈 |
112 (0x70) --> 115 (0x73) | _rem | : 将栈顶两个对应类型数据求余,再将结果值入栈 |
116 (0x74) --> 119 (0x77) | _neg | 将栈顶对应类型数据进行取负运算(即-value ),再将结果值入栈 |
120 (0x78)--> 121 (0x79) | _ shl | 将值进行左移运算 |
122 (0x7a)--> 123 (0x7b) | _ shr | 将值进行有符号右移运算 |
124 (0x7c)--> 125 (0x7d) | _ ushr | 将值进行无符号右移运算 |
126 (0x7e) --> 127 (0x7f) | _and | 对值进行按位与运算 |
128 (0x80) --> 129 (0x81) | _or | 对值进行按位或运算 |
130 (0x82) --> 131 (0x83) | _xor | 对值进行按位异或运算 |
132 (0x84) | iinc | 直接将局部变量表对应索引处的值增加值 |
7.1 _add
- 指令数据
_add
- 操作数栈变化
..., value1, value2 → ..., result
- 包括指令
iadd = 96 (0x60) ladd = 97 (0x61) fadd = 98 (0x62) dadd = 99 (0x63)
- 作用: 将栈顶两个对应类型数据相加,再将结果值入栈,结果值还是对应类型。
7.2 _sub
- 指令数据
_sub
- 操作数栈变化
..., value1, value2 → ..., result
- 包括指令
isub = 100 (0x64) lsub = 101 (0x65) fsub = 102 (0x66) dsub = 103 (0x67)
- 作用: 将栈顶两个对应类型数据相减,再将结果值入栈,结果值还是对应类型。
7.3 _mul
- 指令数据
_mul
- 操作数栈变化
..., value1, value2 → ..., result
- 包括指令
imul= 104 (0x68) lmul = 105 (0x69) fmul = 106 (0x6a) dmul = 107 (0x6b)
- 作用: 将栈顶两个对应类型数据相乘,再将结果值入栈,结果值还是对应类型。
7.4 _div
- 指令数据
_div
- 操作数栈变化
..., value1, value2 → ..., result
- 包括指令
idiv= 108 (0x6c) ldiv = 109 (0x6d) fdiv = 110 (0x6e) ddiv = 111 (0x7f)
- 作用: 将栈顶两个对应类型数据相除,再将结果值入栈,结果值还是对应类型。
7.5 _rem
- 指令数据
_rem
- 操作数栈变化
..., value1, value2 → ..., result
- 包括指令
irem= 112 (0x70) lrem = 113 (0x71) frem = 114 (0x72) drem = 115 (0x73)
- 作用: 将栈顶两个对应类型数据求余,再将结果值入栈,结果值还是对应类型。
float
和double
类型的求余,是会先经过数值集合转换成对应的int
和long
类型,进行求余,然后再转换为float
和double
类型。
7.6 _neg
- 指令数据
_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 _ushr
- 指令数据
_ushr
- 操作数栈变化
..., value1, value2 → ..., result
- 包括指令
124 (0x7c) iushr 125 (0x7d) lushr
- 作用: 将
int
或long
类型的值进行无符号右移运算,即用高位补零的方式进行右移。- 我们知道正数的最高位是
0
,负数的最高位是1
;我们进行右移运算时,原来的最高位会右移一位,那么新的最高位补什么呢? - 如果使用
_shr
指令,新的最高位补就和原先最高位是一样的;也就是说正数是补零,负数就补1
。 - 如果使用
_ushr
指令,不管是正数还是负数,新的最高位补只会是零。 - 所以你会发现
_shr
指令,调用很多次后,最后结果值一定是-1
;而_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 _and
- 指令数据
_and
- 操作数栈变化
..., value1, value2 → ..., result
- 包括指令
126 (0x7e) iand 127 (0x7f) land
- 作用: 对
int
或者long
进行按位与运算。value1
和value2
必须是同一种类型,结果值也是这种类型。
7.13 _or
- 指令数据
_or
- 操作数栈变化
..., value1, value2 → ..., result
- 包括指令
128 (0x80) ior 129 (0x81) lor
- 作用: 对
int
或者long
进行按位或运算。value1
和value2
必须是同一种类型,结果值也是这种类型。
7.14 _xor
- 指令数据
_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
17 iconst_1 18 iadd 19 putfield #2 // ci ++ 结束 // si ++ 开始 22 getstatic #3 25 iconst_1 26 iadd 27 putstatic #3 // si ++ 结束 30 return
7.16 小结
大部分 Math
类型指令都是对栈中两个数进行运算,再将结果值入栈
..., value1, value2 →
..., result
只有两个类型指令不一样,那就是 _neg
和 iinc
-
_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 |
比较两个 float 类型的大小 |
151 (0x97) -> 152 (0x98) | dcmp |
比较两个 double 类型的大小 |
153 (0x99) -> 158 (0x9e) | if |
int 数据与零比较的条件分支判断 |
159 (0x9f) -> 164 (0xa4) | if_icmp |
int 类型比较的条件分支判断 |
165 (0xa5) -> 166 (0xa6) | if_acmp |
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
- 指令数据
fcmp
- 操作数栈变化
..., 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
- 指令数据
dcmp
- 操作数栈变化
..., 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
- 指令数据
dcmp
branchbyte1 branchbyte2 - 操作数栈变化
..., value → ...
- 包括指令
ifeq = 153 (0x99) ifne = 154 (0x9a) iflt = 155 (0x9b) ifge = 156 (0x9c) ifgt = 157 (0x9d) ifle = 158 (0x9e)
- 作用:
int
数据与零比较的条件分支判断value
必须是int
类型,然后与零进行比较:-
ifeq
succeeds if and only ifvalue = 0
-
ifne
succeeds if and only ifvalue ≠ 0
-
iflt
succeeds if and only ifvalue < 0
-
ifle
succeeds if and only ifvalue ≤ 0
-
ifgt
succeeds if and only ifvalue > 0
-
ifge
succeeds if and only ifvalue ≥ 0
如果比较结果为真,那么程序就跳转到由branchbyte1
和branchbyte2
构造的两个字节无符号指定地址执行,如果为假,那么就 继续执行if
指令后面的其他指令。
-
9.4 if_icmp
- 指令数据
if_icmp
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
指令的操作逻辑是一样的,if
指令就相当于value2
为零的if_icmp
特殊格式。-
if_icmpeq
succeeds if and only ifvalue1 = value2
-
if_icmpne
succeeds if and only ifvalue1 ≠ value2
-
if_icmplt
succeeds if and only ifvalue1 < value2
-
if_icmple
succeeds if and only ifvalue1 ≤ value2
-
if_icmpgt
succeeds if and only ifvalue1 > value2
-
if_icmpge
succeeds if and only ifvalue1 ≥ value2
-
9.5 if_acmp
- 指令数据
if_acmp
branchbyte1 branchbyte2 - 操作数栈变化
..., value1, value2 → ...
- 包括指令
if_acmpeq = 165 (0xa5) if_acmpne = 166 (0xa6)
- 作用:
reference
类型比较的条件分支判断reference
类型只有相等和不等的条件判断,还有是否为null
的条件判断,这个在Extended
扩展类型指令中。-
if_acmpeq
succeeds if and only ifvalue1 = value2
-
if_acmpne
succeeds 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
和if_icmp
都表示int
类型比较的条件分支判断,只不过if
只与零进行比较。
它们的操作数栈变化
根据比较结果进行程序跳转。..., 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) | _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 _return
- 指令数据
_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 | 用于调用实例构造器 方法、私有方法和父类中的方法 |
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 ...]] → ...
- 作用: 用于调用实例构造器
方法、私有方法和父类中的方法。()
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
和<>_store
indexbyte1 indexbyte2 类型二:
wide
iinc
indexbyte1 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 - 操作数栈变化
没有任何变化 ...
- 作用: 无条件跳转到四个字节组成的无符号指令地址处。