JVM 字节码指令活用手册(基于 Java 17 SE 规范)

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                       // 弹出构造函数返回值

栈变化图解

  1. new: → ref
  2. dup: ref → ref, ref
  3. invokespecial: ref, ref → ref (第一个 ref 被消耗,第二个 ref 留在栈顶)
  4. 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 intlong i → l 符号扩展 long l = (long)i;
i2f intfloat i → f 可能丢失精度(但范围扩大) float f = (float)i;
i2d intdouble i → d 无损失 double d = (double)i;
l2f longfloat l → f 可能丢失精度 float f = (float)l;
l2d longdouble l → d 无损失 double d = (double)l;
f2d floatdouble f → d 无损失 double d = (double)f;
窄化转换(可能损失精度/符号)
l2i longint l → i 截断高位 int i = (int)l;
f2i floatint f → i 截断小数,溢出返回 Integer.MIN_VALUE/MAX_VALUE int i = (int)f;
d2i doubleint d → i 同上 int i = (int)d;
窄化整数转换(截断)
i2b intbyte(结果转 int i → i 截断为 8 位 byte b = (byte)i;
i2c intchar(结果转 int i → i 截断为 16 位,无符号 char c = (char)i;
i2s intshort(结果转 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-elseelse 分支
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>),大常量用 ldcnullaconst_null
  • 加载存储 :类型前缀 (i/l/f/d/a) + load/store_<i> 是 0-3 速记,数组操作带 aiaload)。
  • 栈管理pop 弹,dup 复制,swap 交换,nop 占位,dup_x* 是高级技巧。
  • 数学运算 :类型前缀 + 操作(iadd),iinc 直接改局部变量。
  • 对象操作new 创对象,newarray 创数组,checkcast 检类型,instanceof 做判断。
  • 方法调用virtual 虚调用,special 静态派,static 调静态,interface 调接口,dynamic 动态联。
  • 控制流if 开头条件跳,goto 无条件,tableswitch 连续 case,lookupswitch 稀疏 case。
  • 返回指令 :类型前缀 + returnvoid 直接 return

相关推荐
刘 大 望1 小时前
JVM(Java虚拟机)
java·开发语言·jvm·数据结构·后端·java-ee
元亓亓亓1 小时前
LeetCode热题100--155. 最小栈--中等
java·算法·leetcode
丸码1 小时前
JVM演进史:从诞生到革新
jvm
SadSunset1 小时前
(3)第一个spring程序
java·后端·spring
子午1 小时前
【垃圾识别系统】Python+TensorFlow+Django+人工智能+深度学习+卷积神经网络算法
人工智能·python·深度学习
高山上有一只小老虎1 小时前
小红的双生串
java·算法
CHANG_THE_WORLD1 小时前
Python 推导式详细教程
开发语言·python
TDengine (老段)1 小时前
人力减 60%:时序数据库 TDengine 助力桂冠电力实现 AI 智能巡检
java·大数据·数据库·人工智能·时序数据库·tdengine·涛思数据
ljh5746491191 小时前
用vscode怎么运行conda中的python环境
vscode·python·conda