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 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。

相关推荐
IT_Octopus1 小时前
【零基础入门 LLM 开发 · Day 13】:LangChain 的对话记忆——Message 对象版
人工智能·python·langchain
哦虎!1 小时前
【JavaEE】网络初识
java
2603_965148111 小时前
社区与学习资源:从入门到精通的API选品学习路径
数据库·人工智能·python·学习·数据挖掘
for_ever_love__1 小时前
MySQL ORDER BY 排序优化:从 Using filesort 到索引有序扫描(含双路 / 单路排序原理)
java·mysql·优化·存储·by·order·filesort
我是人✓2 小时前
Java文件上传三方式对比:从传统到SpringBoot
java·开发语言
欣栀♡2 小时前
JVM 频繁 Full GC 先看日志括号里的 cause,jstat 的 FGC 在 JDK 8 上会骗人
java·jvm
CCblandy3 小时前
令牌桶在 0.9 倍负载超发 19%、固定窗口边界放行 20 个:四种限流算法同构实测
后端·python·算法·性能优化·架构
IT研究室3 小时前
最新计算机毕业设计选题推荐-基于spring boot的葡萄酒文化宣传网站-网站-文档指导-Java-springboot
java·spring boot·课程设计
慧都小项3 小时前
MyEclipse 中创建和测试 JAX-RS REST 接口:从项目配置到 REST Explorer
java·restful·myeclipse·jax-rs
Wang's Blog3 小时前
Java 项目实战: 交友项目 - 入门案例:编写 Dubbo 服务提供者
java·dubbo·交友