JVM 字节码指令活用手册(基于 Java 17 SE 规范)
本手册基于 Java SE 17 JVM 指令集,以**“从代码到字节码”**为核心视角,按照开发者最常见的理解路径重新组织结构,适用于:
- Java/JVM 学习者:建立从源码到底层的认知桥梁。
- 字节码工程(ASM/ByteBuddy)开发者:快速定位指令,理解其精确行为。
- Agent/注解处理器/编译器相关从业者:实现代码生成、分析与修改。
- 逆向分析、性能调优、面试准备者:洞察 Java 代码的底层执行逻辑。
阅读模型:JVM = 局部变量表 + 操作数栈 + 常量池
所有 JVM 指令都可归入一个简单的数据流体系:
局部变量表 ←→ 操作数栈 ←→ 常量池/对象/方法区
- 90% 的指令:将“某个位置的值”移动到另一个位置并进行加工。
- 任何复杂操作:都是对这个三元模型的组合。
指令格式说明
| 字段 |
含义 |
| 指令 |
JVM opcode 本体 |
| 描述 |
指令做什么,是否涉及局部变量/栈/对象/跳转 |
| 操作数 |
指令后跟随的字节(若有),如常量池索引、局部变量槽号 |
| 栈行为 |
入栈/出栈顺序,用 ... → ... 表示,... 代表栈顶到栈底 |
| 实战场景 |
该指令在 Java 代码中出现的典型场景 |
| 异常 |
哪些情况会抛异常(如 idiv 除零、数组越界) |
1. 常量加载(推常量入栈)
核心思想:将常量池或指令内嵌的值推入操作数栈,是计算的“源头”。
| 指令 |
描述 |
操作数 |
栈行为 |
实战场景 |
异常 |
aconst_null |
推入 null 引用 |
无 |
→ null |
Object o = null; |
无 |
iconst_m1 |
推入 int 常量 -1 |
无 |
→ -1 |
int i = -1; |
无 |
iconst_<i> |
推入 int 常量 <i>(i ∈ [0,5]) |
无 |
→ i |
int i = 0; / if (i == 1) ... |
无 |
lconst_<l> |
推入 long 常量 <l>(l ∈ {0L,1L}) |
无 |
→ l |
long l = 0L; |
无 |
fconst_<f> |
推入 float 常量 <f>(f ∈ {0.0,1.0,2.0}) |
无 |
→ f |
float f = 1.0f; |
无 |
dconst_<d> |
推入 double 常量 <d>(d ∈ {0.0,1.0}) |
无 |
→ d |
double d = 0.0; |
无 |
bipush |
将 byte 值(-128~127)作为 int 推入栈 |
1 字节:byte 常量值 |
→ val |
int i = 100; |
无 |
sipush |
将 short 值(-32768~32767)作为 int 推入栈 |
2 字节:short 常量值 |
→ val |
int i = 20000; |
无 |
ldc |
从常量池推入 int/float/String/Class 引用(索引 ≤ 255) |
1 字节:常量池索引 |
→ value |
String s = "hello"; / Class c = String.class; |
无 |
ldc_w |
同上,但支持常量池索引 > 255 |
2 字节:常量池索引 |
→ value |
当常量池很大时 |
无 |
ldc2_w |
从常量池推入 long/double(索引 > 255,仅 2 字节索引) |
2 字节:常量池索引 |
→ value |
long l = 123456789L; / double d = 3.14; |
无 |
设计哲学:iconst_<i>, iload_<i> 等指令是为了节省字节码空间。最常见的 0-3 号局部变量和 -1 到 5 的常量,用单字节指令表示,而不是 iload 0(2字节)。
2. 加载与存储(局部变量表 ↔ 操作数栈)
2.1 局部变量加载(从局部变量表到栈)
| 指令 |
描述 |
操作数 |
栈行为 |
实战场景 |
异常 |
iload_<i> |
加载局部变量 <i>(i ∈ [0,3])的 int 值 |
无 |
→ val |
方法参数、局部变量的读取 |
无 |
lload_<i> |
加载局部变量 <i> 的 long 值 |
无 |
→ val |
同上,long 类型 |
无 |
fload_<i> |
加载局部变量 <i> 的 float 值 |
无 |
→ val |
同上,float 类型 |
无 |
dload_<i> |
加载局部变量 <i> 的 double 值 |
无 |
→ val |
同上,double 类型 |
无 |
aload_<i> |
加载局部变量 <i> 的引用值 |
无 |
→ ref |
对象、数组、this 引用 |
无 |
iload |
加载指定索引的 int 局部变量 |
1 字节:局部变量索引 |
→ val |
局部变量索引 > 3 时 |
无 |
lload/fload/dload/aload |
同上,对应 long/float/double/引用 |
1 字节:局部变量索引 |
→ val/ref |
同上 |
无 |
2.2 数组元素加载(从数组到栈)
| 指令 |
描述 |
栈行为 |
实战场景 |
异常 |
iaload |
从 int[] 加载元素 |
arrayref, index → val |
int x = arr[0]; |
NullPointerException, ArrayIndexOutOfBoundsException |
laload/faload/daload |
从 long[]/float[]/double[] 加载元素 |
同上 |
同上,对应类型 |
同上 |
aaload |
从引用数组(如 Object[])加载元素 |
arrayref, index → ref |
Object o = arr[0]; |
同上 |
baload/caload/saload |
从 byte[]/char[]/short[] 加载元素(结果转 int) |
同上 |
byte b = barr[0]; (结果为 int 类型) |
同上 |
2.3 局部变量存储(从栈到局部变量表)
| 指令 |
描述 |
操作数 |
栈行为 |
实战场景 |
异常 |
istore_<i> |
栈顶 int 存入局部变量 <i>(i ∈ [0,3]) |
无 |
val → |
int x = 10; (赋值后) |
无 |
lstore_<i>/fstore_<i>/dstore_<i> |
栈顶 long/float/double 存入局部变量 <i> |
无 |
val → |
同上,对应类型 |
无 |
astore_<i> |
栈顶引用存入局部变量 <i> |
无 |
ref → |
String s = "hi"; (赋值后) |
无 |
istore |
栈顶 int 存入指定索引的局部变量 |
1 字节:局部变量索引 |
val → |
局部变量索引 > 3 时 |
无 |
lstore/fstore/dstore/astore |
同上,对应 long/float/double/引用 |
1 字节:局部变量索引 |
val/ref → |
同上 |
无 |
2.4 数组元素存储(从栈到数组)
| 指令 |
描述 |
栈行为 |
实战场景 |
异常 |
iastore |
将 int 值存入 int[] |
arrayref, index, val → |
arr[0] = 10; |
NullPointerException, ArrayIndexOutOfBoundsException |
lastore/fastore/dastore |
将 long/float/double 值存入对应数组 |
同上 |
同上,对应类型 |
同上 |
aastore |
将引用值存入引用数组 |
arrayref, index, ref → |
objArr[0] = new Object(); |
同上, ArrayStoreException |
bastore/castore/sastore |
将 int 值(截断)存入 byte[]/char[]/short[] |
arrayref, index, val → |
barr[0] = 10; (val 是 int 类型) |
同上 |
示例:数组操作
public void arrayOps() {
int[] arr = new int[3];
arr[0] = 100;
int val = arr[0];
}
// 对应字节码
0: iconst_3 // → 3
1: newarray 10 (int) // 3 → int[3] 的引用
3: astore_1 // int[3] 引用 → 局部变量 1 (arr)
4: aload_1 // 局部变量 1 → int[3] 引用
5: iconst_0 // → 0
6: bipush 100 // → 100
8: iastore // int[3] 引用, 0, 100 → (存入数组)
9: aload_1 // 局部变量 1 → int[3] 引用
10: iconst_0 // → 0
11: iaload // int[3] 引用, 0 → 100
12: istore_2 // 100 → 局部变量 2 (val)
13: return
3. 操作数栈管理
核心思想:直接操作栈,用于准备参数、交换值、复制值,是字节码变形的“瑞士军刀”。
| 指令 |
描述 |
栈行为 (栈顶→栈底) |
实战场景 |
异常 |
pop |
弹出栈顶 1 个值 (category 1) |
a, ... → ... |
丢弃一个无用的返回值,如 sb.toString(); 的结果 |
无 |
pop2 |
弹出栈顶 1 个 category 2 值 或 2 个 category 1 值 |
a,b, ... → ... 或 L, ... → ... |
丢弃 long/double 返回值或两个 int 值 |
无 |
dup |
复制栈顶值并推入 |
a, ... → a, a, ... |
交换两个局部变量,或用于 new 后的 dup |
无 |
dup_x1 |
复制栈顶值,插入到栈顶第二个值之下 |
a,b, ... → a,b,a, ... |
交换栈顶两个值 (swap 的替代实现) |
无 |
dup_x2 |
复制栈顶值,插入到栈顶第二/三个值之下 |
a,b,c, ... → a,b,c,a, ... |
复杂交换操作 |
无 |
dup2 |
复制栈顶 1 个 category 2 或 2 个 category 1 值 |
a,b, ... → a,b,a,b, ... |
交换两个 long/double 值 |
无 |
dup2_x1 |
复制栈顶 2 个槽,插入到下方 |
a,b,c, ... → a,b,c,a,b, ... |
复杂交换操作 |
无 |
dup2_x2 |
复制栈顶 2 个槽,插入到更下方 |
a,b,c,d, ... → a,b,c,d,a,b, ... |
极其复杂的交换操作,通常由编译器生成 |
无 |
swap |
交换栈顶两个 category 1 值 |
a,b, ... → b,a, ... |
交换两个局部变量的值 |
无 |
示例:new 对象并初始化
new 指令只分配内存,不调用构造函数。构造函数调用需要对象引用。
new Object();
// 对应字节码
0: new #2 // class java/lang/Object
3: dup // 复制对象引用,一份用于 invokespecial,一份作为表达式结果
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: pop // 弹出构造函数返回值
栈变化图解:
new: → ref
dup: ref → ref, ref
invokespecial: ref, ref → ref (第一个 ref 被消耗,第二个 ref 留在栈顶)
pop: ref →
4. 数学运算与类型转换
4.1 数学运算(栈顶值计算)
| 指令 |
描述 |
栈行为 |
实战场景 |
可能异常 |
| 加法 |
iadd/ladd/fadd/dadd |
a,b → a+b |
c = a + b; |
无(浮点溢出返回特殊值) |
| 减法 |
isub/lsub/fsub/dsub |
a,b → a-b |
c = a - b; |
无 |
| 乘法 |
imul/lmul/fmul/dmul |
a,b → a*b |
c = a * b; |
无 |
| 除法 |
idiv/ldiv/fdiv/ddiv |
a,b → a/b |
c = a / b; |
idiv/ldiv 除数为 0 抛出 ArithmeticException |
| 取余 |
irem/lrem/frem/drem |
a,b → a%b |
c = a % b; |
同上 |
| 取反 |
ineg/lneg/fneg/dneg |
a → -a |
c = -a; |
无 |
| 增量 |
iinc |
直接修改局部变量 |
for (int i=0;...; i++) 中的 i++ |
无 |
4.2 类型转换(栈顶值转换)
| 指令 |
描述 |
栈行为 |
转换规则 |
实战场景 |
| 宽化转换(无精度损失) |
|
|
|
|
i2l |
int → long |
i → l |
符号扩展 |
long l = (long)i; |
i2f |
int → float |
i → f |
可能丢失精度(但范围扩大) |
float f = (float)i; |
i2d |
int → double |
i → d |
无损失 |
double d = (double)i; |
l2f |
long → float |
l → f |
可能丢失精度 |
float f = (float)l; |
l2d |
long → double |
l → d |
无损失 |
double d = (double)l; |
f2d |
float → double |
f → d |
无损失 |
double d = (double)f; |
| 窄化转换(可能损失精度/符号) |
|
|
|
|
l2i |
long → int |
l → i |
截断高位 |
int i = (int)l; |
f2i |
float → int |
f → i |
截断小数,溢出返回 Integer.MIN_VALUE/MAX_VALUE |
int i = (int)f; |
d2i |
double → int |
d → i |
同上 |
int i = (int)d; |
| 窄化整数转换(截断) |
|
|
|
|
i2b |
int → byte(结果转 int) |
i → i |
截断为 8 位 |
byte b = (byte)i; |
i2c |
int → char(结果转 int) |
i → i |
截断为 16 位,无符号 |
char c = (char)i; |
i2s |
int → short(结果转 int) |
i → i |
截断为 16 位 |
short s = (short)i; |
5. 对象创建与操作
| 指令 |
描述 |
操作数 |
栈行为 |
实战场景 |
可能异常 |
new |
创建对象实例(不调用构造函数) |
2 字节:常量池索引(类符号引用) |
→ ref |
new MyClass(); |
OutOfMemoryError |
newarray |
创建基本类型数组 |
1 字节:元素类型编码(如 T_INT=10) |
size → arrayref |
new int[10]; |
NegativeArraySizeException |
anewarray |
创建引用类型数组 |
2 字节:常量池索引(元素类型符号引用) |
size → arrayref |
new String[10]; |
同上 |
multianewarray |
创建多维数组 |
2 字节:常量池索引;1 字节:维度 |
size1, size2... → arrayref |
new int[2][3]; |
同上 |
arraylength |
获取数组长度(结果为 int) |
无 |
arrayref → len |
int len = arr.length; |
NullPointerException |
athrow |
抛出栈顶异常对象 |
无 |
ref → (异常抛出) |
throw new Exception(); |
无 |
checkcast |
检查类型转换合法性 |
2 字节:常量池索引(目标类型符号引用) |
ref → ref |
(String) obj; |
ClassCastException |
instanceof |
检查对象是否为指定类型实例(结果 int 0/1) |
2 字节:常量池索引(类型符号引用) |
ref → 0/1 |
if (obj instanceof String) {...} |
无 |
getclass |
获取对象的运行时类(Class 引用) |
无 |
ref → classref |
Class c = obj.getClass(); |
NullPointerException |
monitorenter |
进入对象监视器(synchronized 块开始) |
无 |
ref → |
synchronized(obj) { ... } |
NullPointerException |
monitorexit |
退出对象监视器(synchronized 块结束) |
无 |
ref → |
synchronized 块结束处 |
同上 |
6. 字段与方法调用
6.1 字段访问
| 指令 |
描述 |
操作数 |
栈行为 |
实战场景 |
可能异常 |
getstatic |
获取静态字段值 |
2 字节:常量池索引 |
→ value |
int x = MyClass.staticField; |
无(链接时验证) |
putstatic |
存入静态字段值 |
同上 |
value → |
MyClass.staticField = x; |
无 |
getfield |
获取实例字段值 |
同上 |
ref → value |
int x = obj.instanceField; |
NullPointerException |
putfield |
存入实例字段值 |
同上 |
ref, value → |
obj.instanceField = x; |
同上 |
6.2 方法调用
| 指令 |
描述 |
操作数 |
栈行为 |
实战场景 |
调用机制 |
invokevirtual |
调用实例方法(动态分派) |
2 字节:常量池索引 |
this, args... → result |
obj.normalMethod(); |
基于对象实际类型查找方法(虚调用) |
invokespecial |
调用实例方法(静态分派) |
同上 |
this, args... → result |
super.method(); / 构造函数调用 / private 方法调用 |
不动态分派,直接绑定符号引用中的方法 |
invokestatic |
调用静态方法 |
同上 |
args... → result |
MyClass.staticMethod(); |
静态绑定,无 this |
invokeinterface |
调用接口方法(动态分派) |
同上;1 字节:参数数量;1 字节:0 |
this, args... → result |
myInterfaceMethod(); |
基于对象实际类型查找实现类方法,比 invokevirtual 稍慢 |
invokedynamic |
动态调用(Lambda/动态语言基础) |
2 字节:常量池索引;2 字节:0 |
args... → result |
() -> System.out.println("Lambda"); |
首次执行时通过引导方法链接目标方法 |
示例:多态调用 (invokevirtual)
class Parent { void who() { System.out.println("Parent"); } }
class Child extends Parent { @Override void who() { System.out.println("Child"); } }
public void test() {
Parent p = new Child();
p.who();
}
// 对应字节码
0: new #2 // class Child
3: dup
4: invokespecial #3 // Method Child."<init>":()V
7: astore_1
8: aload_1 // 加载 Parent 类型的引用 p
9: invokevirtual #4 // Method Parent.who:()V // 关键:调用 Parent.who,但实际执行 Child.who
12: return
7. 控制流与返回
7.1 条件跳转
| 指令类型 |
指令 |
描述 |
栈行为 |
实战场景 |
| int 与 0 比较 |
ifeq/ifne |
栈顶 int 等于/不等于 0 则跳转 |
i → |
if (i == 0) ... / if (i != 0) ... |
|
iflt/ifle/ifgt/ifge |
栈顶 int < / ≤ / > / ≥ 0 则跳转 |
i → |
if (i < 0) ... |
| int 间比较 |
if_icmpeq/if_icmpne |
栈顶两个 int 相等/不相等则跳转 |
i1, i2 → |
if (a == b) ... |
|
if_icmplt/if_icmple/if_icmpgt/if_icmpge |
栈顶两个 int < / ≤ / > / ≥ 则跳转 |
i1, i2 → |
if (a < b) ... |
| 引用比较 |
if_acmpeq/if_acmpne |
栈顶两个引用相等/不相等则跳转 |
ref1, ref2 → |
if (a == b) ... (对象引用) |
| null 判断 |
ifnull/ifnonnull |
栈顶引用为 null/非 null 则跳转 |
ref → |
if (obj != null) ... |
7.2 无条件跳转与 switch
| 指令 |
描述 |
操作数 |
实战场景 |
goto |
无条件跳转 |
2 字节:相对偏移量 |
for/while 循环、if-else 的 else 分支 |
goto_w |
宽索引版无条件跳转 |
4 字节:相对偏移量 |
方法过长,goto 偏移量不够时 |
tableswitch |
连续 case 值的高效 switch |
默认偏移量、case 最小值、case 最大值、case 偏移量表 |
switch (day) { case 1: ... case 2: ... } |
lookupswitch |
稀疏 case 值的 switch |
默认偏移量、case 数量、case-value-偏移量对列表 |
switch (type) { case 1: ... case 100: ... } |
示例:if-else
if (a > b) {
c = 1;
} else {
c = 2;
}
// 对应字节码
iload_0 // → a
iload_1 // → b
if_icmple 11 // a, b → (如果 a <= b, 跳转到偏移量 11)
iconst_1 // → 1
istore_2 // 1 → c
goto 13 // 跳转到结尾
11: iconst_2 // → 2
istore_2 // 2 → c
13: ...
7.3 返回指令
| 指令 |
描述 |
栈行为 |
适用方法类型 |
ireturn |
返回 int 值 |
i → (返回给调用者) |
int 返回值方法 |
lreturn |
返回 long 值 |
l → |
long 返回值方法 |
freturn |
返回 float 值 |
f → |
float 返回值方法 |
dreturn |
返回 double 值 |
d → |
double 返回值方法 |
areturn |
返回引用值 |
ref → |
引用类型返回值方法 |
return |
返回 void |
→ |
void 方法 |
8. 扩展与调试指令
| 指令 |
描述 |
操作数 |
实战场景 |
wide |
扩展后续指令的局部变量索引为 16 位 |
1 字节:被扩展的指令;2 字节:局部变量索引;若为 iinc 再加 2 字节增量 |
支持局部变量索引 > 255 |
nop |
无操作 |
无 |
调试器占位、代码对齐 |
breakpoint |
调试器断点指令 |
无 |
调试器使用 |
总结:指令助记符速记口诀
- 常量加载:小常量直接推(
iconst_<i>),大常量用 ldc,null 用 aconst_null。
- 加载存储:类型前缀 (
i/l/f/d/a) + load/store,_<i> 是 0-3 速记,数组操作带 a(iaload)。
- 栈管理:
pop 弹,dup 复制,swap 交换,nop 占位,dup_x* 是高级技巧。
- 数学运算:类型前缀 + 操作(
iadd),iinc 直接改局部变量。
- 对象操作:
new 创对象,newarray 创数组,checkcast 检类型,instanceof 做判断。
- 方法调用:
virtual 虚调用,special 静态派,static 调静态,interface 调接口,dynamic 动态联。
- 控制流:
if 开头条件跳,goto 无条件,tableswitch 连续 case,lookupswitch 稀疏 case。
- 返回指令:类型前缀 +
return,void 直接 return。
所有评论(0)