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 类型) |
同上 |
示例:数组操作
java// Java 代码 public void arrayOps() { int[] arr = new int[3]; arr[0] = 100; int val = arr[0]; }
bytecode// 对应字节码 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指令只分配内存,不调用构造函数。构造函数调用需要对象引用。
java// Java 代码 new Object();
bytecode// 对应字节码 0: new #2 // class java/lang/Object 3: dup // 复制对象引用,一份用于 invokespecial,一份作为表达式结果 4: invokespecial #1 // Method java/lang/Object."<init>":()V 7: pop // 弹出构造函数返回值栈变化图解:
new:→ refdup:ref → ref, refinvokespecial: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)
java// Java 代码 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(); // 动态分派 }
bytecode// 对应字节码 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
java// Java 代码 if (a > b) { c = 1; } else { c = 2; }
bytecode// 对应字节码 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。