操作数栈/比较/跳转指令
栈操作指令
在深入控制流之前,先把栈操作指令搞清楚。这些指令用于直接操作操作数栈,不需要经过局部变量表。
栈顶操作:pop / pop2
pop // 弹出栈顶一个槽的值(int/float/reference)
pop2 // 弹出栈顶两个槽的值(long/double,或两个 int)java
// 源码
method(1, 2, 3); // 假设 method 返回 void,但不需要结果
// 字节码
bipush 1
bipush 2
bipush 3
invokevirtual #method
pop // 弹出返回值(void 方法也会入栈再弹出)pop vs pop2:
栈: [value] → pop → [empty] (int/float/reference)
栈: [v1, v2] → pop2 → [empty] (long/double)
栈: [v1, v2] → pop2 → [empty] (两个 int/float/reference)交换:swap
swap // 交换栈顶两个槽的值(类型必须相同或兼容)java
// 源码:交换两个值
int a = x;
int b = y;
int temp = a;
a = b;
b = temp;
// 字节码(用 swap)
iload_1 // a 入栈
iload_2 // b 入栈
swap // 交换:[b, a]
istore_1 // a = b
istore_2 // b = temp(a)复制:dup 系列
这是最常用也最复杂的栈操作指令家族:
dup // 复制栈顶值
dup_x1 // 复制栈顶值,插入到栈顶第二个值下面
dup_x2 // 复制栈顶值,插入到栈顶第三个值下面
dup2 // 复制栈顶两个值
dup2_x1 // 复制栈顶两个值,插入到栈顶第三个值下面
dup2_x2 // 复制栈顶两个值,插入到栈顶第四个值下面dup 的典型用途:new + 构造函数
这是 dup 最常见的场景:
java
// 源码
User user = new User();
// 字节码
new #User // 创建对象,引用入栈:[ref]
dup // 复制引用:[ref, ref]
invokespecial #init // 调用构造器,弹出第一个 ref:[ref]
astore_1 // 存入 user:[empty]执行过程:
new → 栈: [ref1]
dup → 栈: [ref1, ref2] (ref1 和 ref2 是同一个对象的两个引用)
invokespecial → 栈: [ref1] (弹出 ref2 用于构造器)
astore_1 → 栈: [empty] (存入 ref1)dup_x1 的用途
java
// 源码
long id = getId(); // 假设 getId() 返回 long
setId(id); // 设置 id
// 字节码
invokevirtual #getId // 返回 long 入栈:[l1, l2]
dup2 // 复制两个槽:[l1, l2, l1, l2]
invokevirtual #setId // 调用 setId(long):弹出 [l1, l2],剩 [l1, l2]
lstore_1 // 存入 id:[empty]栈操作指令速查
| 指令 | 效果 | 典型用途 |
|---|---|---|
pop | 弹出一个槽 | 丢弃不需要的返回值 |
pop2 | 弹出两个槽 | 丢弃 long/double 返回值 |
swap | 交换栈顶两个槽 | 交换两个值 |
dup | 复制栈顶一个槽 | new + 构造函数 |
dup_x1 | 复制栈顶,插入第二位 | 方法返回后处理 |
dup2 | 复制栈顶两个槽 | 处理 long/double 返回值 |
比较指令
int 比较:if 系列
ifeq target // 栈顶 == 0 时跳转
ifne target // 栈顶 != 0 时跳转
iflt target // 栈顶 < 0 时跳转
ifle target // 栈顶 <= 0 时跳转
ifgt target // 栈顶 > 0 时跳转
ifge target // 栈顶 >= 0 时跳转注意:这些指令比较的是栈顶值与 0,不是两个栈顶值。
java
// 源码
if (x == 0) { doSomething(); }
// 字节码
iload_1 // x 入栈
ifeq 10 // x == 0 则跳到偏移 10
// 否则继续执行int 比较两个值:if_icmp 系列
if_icmpeq target // 栈顶 == 第二个栈顶值 时跳转
if_icmpne target // 栈顶 != 第二个栈顶值 时跳转
if_icmplt target // 栈顶 < 第二个栈顶值 时跳转
if_icmple target // 栈顶 <= 第二个栈顶值 时跳转
if_icmpgt target // 栈顶 > 第二个栈顶值 时跳转
if_icmpge target // 栈顶 >= 第二个栈顶值 时跳转java
// 源码
if (a > b) { return 1; } else { return 0; }
// 字节码
iload_1 // a 入栈:[a]
iload_2 // b 入栈:[a, b]
if_icmpgt 12 // a > b 则跳转:[a, b] 被弹出
iconst_0 // else: 0 入栈
ireturn // 返回 0
iconst_1 // if: 1 入栈
ireturn // 返回 1执行过程图示:
iload_1: [a]
iload_2: [a, b]
if_icmpgt 12: 如果 a > b,跳到偏移 12 执行 iconst_1
否则继续执行 iconst_0 + ireturnreference 比较
if_acmpeq target // 两个引用相等时跳转
if_acmpne target // 两个引用不等时跳转java
// 源码
if (obj1 == obj2) { ... }
// 字节码
aload_1 // obj1 入栈
aload_2 // obj2 入栈
if_acmpeq 20 // 相等则跳转比较零值与比较两值的对比
| 指令 | 比较的是什么 | 操作数 |
|---|---|---|
ifeq | 栈顶值 == 0 | 只需弹出一个值 |
if_icmpeq | 栈顶 == 第二个栈顶值 | 需弹出两个值 |
跳转指令
无条件跳转:goto
goto target // 无条件跳转到目标偏移
goto_w target // 宽化跳转,支持更大的偏移范围java
// 源码
while (true) {
// 循环体
}
goto 0 // 字节码中的无条件跳转条件跳转:if 系列
条件跳转和 if 系列比较指令配合使用:
ifeq label1 // == 0
ifne label1 // != 0
iflt label1 // < 0
ifle label1 // <= 0
ifgt label1 // > 0
ifge label1 // >= 0
if_icmpeq label1 // == (两个 int)
if_icmpne label1 // != (两个 int)
if_icmplt label1 // < (两个 int)
if_icmple label1 // <= (两个 int)
if_icmpgt label1 // > (两个 int)
if_icmpge label1 // >= (两个 int)switch 指令:tableswitch 和 lookupswitch
tableswitch:稀疏跳转表
当 case 值密集时使用(如 switch 枚举的 int):
java
// 源码
switch (x) {
case 0: return 0;
case 1: return 10;
case 2: return 20;
case 3: return 30;
default: return -1;
}字节码使用 tableswitch:
tableswitch 0 to 3
0: 偏移 16
1: 偏移 24
2: 偏移 32
3: 偏移 40
default: 偏移 48特点:访问时间 O(1),但如果有大量空 case 会浪费空间。
lookupswitch:稀疏跳转表
当 case 值稀疏时使用:
java
// 源码
switch (c) {
case 'A': return 1; // ASCII 65
case 'Z': return 26; // ASCII 90
case 'a': return 27; // ASCII 97
default: return 0;
}字节码使用 lookupswitch:
lookupswitch
65: 偏移 16 (case 'A')
90: 偏移 24 (case 'Z')
97: 偏移 32 (case 'a')
default: 偏移 40特点:需要线性搜索或哈希查找,但节省空间。
goto_w:宽化跳转
当跳转目标超出 16 位偏移范围时:
goto_w 100000 // 跳转到偏移 100000(超出 goto 的 16 位范围)if-else 的字节码实现
简单 if
java
// 源码
if (x > 0) {
result = x;
}iload_1 // x 入栈
ifngt 8 // x > 0 则跳转
bipush 0 // x <= 0,result = 0
istore_2
iconst_1 // x > 0,跳到这里,result = x
istore_2if-else
java
// 源码
if (x > 0) {
result = x;
} else {
result = -x;
}iload_1 // x 入栈
ifgt 12 // x > 0 则跳到 12
iload_1 // else 分支:x 入栈
ineg // -x
goto 15 // 跳过 if 分支
iconst_0 // (x <= 0,不执行)
nop //
iload_1 // if 分支:x 入栈
istore_2 // result = x三元表达式
java
// 源码
int max = (a > b) ? a : b;iload_1 // a 入栈
iload_2 // b 入栈
if_icmpgt 14 // a > b 则跳转
iload_2 // else: b 入栈
goto 17 // 跳过 if 分支
iload_1 // if: a 入栈
istore_3 // 存入 max三元表达式和 if-else 的字节码几乎一样。
循环的字节码实现
while 循环
java
// 源码
int i = 0;
while (i < 10) {
i++;
}字节码:
iconst_0 // i = 0
istore_1
goto 10 // 跳到条件判断
iload_1 // 循环体开始
iconst_1
iadd
istore_1 // i++
iload_1 // 条件判断
bipush 10
if_icmplt 0 // i < 10 则跳回循环体(偏移 0)
// 循环结束for 循环
java
// 源码
for (int i = 0; i < 10; i++) {
System.out.println(i);
}字节码结构:
初始化部分(循环外)
goto 条件判断
循环体
递增/递减部分
条件判断 → 循环体 or 跳出do-while 循环
java
// 源码
int i = 0;
do {
i++;
} while (i < 10);iconst_0
istore_1 // i = 0
iload_1 // 循环体开始
iconst_1
iadd
istore_1 // i++
iload_1 // 条件判断
bipush 10
if_icmplt 0 // i < 10 则跳转回偏移 0do-while 和 while 的区别:do-while 循环体至少执行一次,所以不需要在开头跳到条件判断。
完整示例
java
// 源码
public static int fibonacci(int n) {
if (n <= 1) {
return n;
}
int a = 0, b = 1;
for (int i = 2; i <= n; i++) {
int temp = a + b;
a = b;
b = temp;
}
return b;
}字节码分析(关键部分):
bash
javap -c FibDemo.class
# public static int fibonacci(int);
# Code:
# 0: iload_0 // n 入栈
# 1: iconst_1 // 1 入栈
# 2: if_icmple 22 // n <= 1 则跳到偏移 22(return n)
#
# 5: iconst_0 // a = 0
# 6: istore_1
# 7: iconst_1 // b = 1
# 8: istore_2
# 9: iconst_2 // i = 2
# 10: istore_3
# 11: iload_3 // i 入栈
# 12: iload_0 // n 入栈
# 13: if_icmpgt 28 // i > n 则跳出循环
#
# 16: iload_1 // temp = a + b
# 17: iload_2
# 18: iadd
# 19: istore 4
# 21: iload_2 // a = b
# 22: istore_1
# 23: iload 4 // b = temp
# 25: istore_2
# 26: iinc 3 by 1 // i++
# 29: goto 11 // 跳回条件判断
#
# 32: iload_2 // return b
# 33: ireturn本节小结
操作数栈/比较/跳转核心要点:
| 类别 | 指令 | 说明 |
|---|---|---|
| 栈操作 | pop/pop2/swap/dup | 直接操作栈 |
| 比较零值 | ifeq/ifne/iflt 等 | 栈顶值与 0 比较 |
| 比较两个值 | if_icmpeq/if_icmpne 等 | 两个栈顶值比较 |
| 引用比较 | if_acmpeq/if_acmpne | 两个引用比较 |
| 无条件跳转 | goto/goto_w | 无条件跳转到目标 |
| switch | tableswitch/lookupswitch | 密集用前者,稀疏用后者 |
理解栈操作和跳转指令的组合,就能分析任何 if/while/for/switch 的字节码实现。
下一节,我们来看 异常处理/同步控制指令。
