JVM规范第 6 章:Java 虚拟机指令集

基于 Oracle 官方《The Java Virtual Machine Specification》第 6 章(Java SE 26)编写。

这是什么,为什么值得懂

读懂本章,你能:

  • javap -c 输出时,知道 0xb6 为什么写成 invokevirtual、它在栈上怎么取参数;
  • 理解「为什么 long/double 在栈上占一项、但某些指令要成对处理」;
  • 真正搞懂 i++ 背后是 iincx = a + b 背后是 iload iload iadd istore
  • 在排查诡异的 StackOverflowErrorVerifyError 时直达字节码层。

1. 指令的物理本质

  • opcode 只占 1 字节(8 位),因此最多 256 种操作码。这就是 JVM 指令集「精简」的根本原因。
  • 一条指令 = 1 字节 opcode + 可选操作数 ;操作数长度因指令而异,所以指令长度可变
  • 操作数大多从操作数栈隐式取得(第 2 章讲过的栈机器模型),而非寄存器编号------这让编译产物紧凑。
  • 少数操作数在编译期 就嵌入指令(如 iload 2 里的 2 是局部变量槽号,ldc #7 里的 #7 是常量池索引)。

1.1 指令描述记法

每条指令条目按统一格式书写:

复制代码
操作码(mnemonic)
  操作码值:十进制 (0x十六进制)
  Forms(家族成员与各自 opcode,如 iconst_<i>)
  操作数栈图:..., in1, in2 → ..., out
  描述
  异常(链接/运行期,此外仅允许 VirtualMachineError 子类)

操作数栈记法 :栈从左向右增长,栈顶在最右。..., value1, value2 → ..., result 表示执行前 value2 在栈顶、value1 次之,执行后两者弹出、压入 resultlong/double 在栈上算一项(但占两个字宽)。

1.2 保留操作码与虚拟机错误

  • 保留操作码 (不出现在合法 class 文件里):0xca breakpoint0xfe impdep10xff impdep2------仅供实现内部调试/扩展用。
  • wide 指令0xc4):为操作数扩成 2 字节,用于访问索引 > 255 的局部变量或常量池项。
  • VirtualMachineError 子类 (指令仅允许抛这些):InternalErrorOutOfMemoryErrorStackOverflowErrorUnknownError

2. 常量与压栈(Constants)

把常量/引用压入操作数栈。小常量有「快车道」专用操作码(第 3 章 useManyNumeric 示例里见过)。

指令 opcode 格式 操作数栈 说明
aconst_null 0x01 --- → null 压入 null 引用
iconst_m1 0x02 --- → -1 int -1
iconst_<i> 0x03--0x08 --- → i (i = 0...5) int 0~5(每值一个 opcode)
lconst_<l> 0x09--0x0a --- → l (l = 0,1) long 0/1
fconst_<f> 0x0b--0x0d --- → f (0/1/2) float 0/1/2
dconst_<d> 0x0e--0x0f --- → d (0/1) double 0/1
bipush 0x10 byte → value 压入 1 字节有符号立即数
sipush 0x11 short → value 压入 2 字节立即数
ldc 0x12 index(1B) → value 从常量池(#index)加载(int/float/String/类)
ldc_w 0x13 index(2B) → value ldc,索引 2 字节
ldc2_w 0x14 index(2B) → value 加载 long/double(第 3、4 章讲过的双槽常量)

记忆法:i/l/f/d/a 分别是 int/long/float/double/reference 的前缀;const 是常量,push 是立即数,ldc 是 load constant(从常量池)。


3. 局部变量加载与存储(Loads / Stores)

局部变量表 (第 2 章 §2.6,槽位 0 是 this)与操作数栈之间搬运值。

指令 opcode 格式 操作数栈 说明
iload 0x15 index → value 加载 int 局部变量(槽 index)
iload_<n> 0x1a--0x1d --- → value (n=0...3) 加载槽 0~3 的 int(省 1 字节操作数)
lload 0x16 index → value 加载 long(占 2 槽)
fload 0x17 index → value 加载 float
dload 0x18 index → value 加载 double(占 2 槽)
aload 0x19 index → value 加载引用(对象/数组)
istore 0x36 index value → 存 int 到局部变量
istore_<n> 0x3b--0x3e --- value → (n=0...3) 存槽 0~3 的 int
lstore/fstore/dstore/astore 0x37/0x38/0x39/0x3a index value → 对应类型存储

数组元素加载/存储(下标在栈上):

指令 opcode 操作数栈 说明
iaload 0x2e arrayref, index → value int 数组取元素
iaload/faload/baload/caload/saload 0x2e--0x35 同构 各基本类型数组取元素
aaload 0x32 arrayref, index → value 引用数组取元素(§6.5 示例:弹出引用数组与 int 索引,取组件引用压栈)
iastore/fastore/bastore/castore/sastore/aastore 0x4f--0x53 arrayref, index, value → 各类型数组存元素

家族规律:a=reference、i=int、l=long、f=float、d=double、b=byte/booleanc=char、s=short;load=取、store=存、aload=a+load。


4. 算术运算(Arithmetic)

全部在操作数栈上完成。整数用补码、浮点用 IEEE 754(第 2 章)。

指令 opcode 操作数栈 说明
iadd 0x60 value1, value2 → result int 加
ladd 0x61 value1, value2 → result long 加
fadd 0x62 → result float 加
dadd 0x63 → result double 加
isub 0x64 value1, value2 → result int 减(value1 - value2)
imul 0x68 → result int 乘
idiv 0x6c value1, value2 → result int 除(抛 ArithmeticException 当除 0)
irem 0x70 → result int 取余
ineg 0x74 value → result int 取负
ishl/ishr/iushr 0x78/0x7a/0x7c value1, value2 → result 左移/算术右移/逻辑右移
iand/ior/ixor 0x7e/0x80/0x82 → result 按位与/或/异或
对应 l/f/d 家族 0x61~0x83 同构 long/float/double 算术

idiv/ldivArithmeticException(属于运行期检查异常,第 2 章讲过)。iinc(见下)是唯一个「直接修改局部变量、不进出栈」的算术指令。

iinc (0x84):index const → 把局部变量 index 的 int 原地加 constvalue → value+const)。这是 i++ 的高效实现,不碰操作数栈。


5. 类型转换(Type Conversion)

指令 opcode 操作数栈 说明
i2l 0x85 value → result int → long
i2f 0x86 → result int → float
i2d 0x87 → result int → double
l2i 0x88 → result long → int(截断)
l2f/l2d 0x89/0x8a → result long → float/double
f2i/f2l/f2d 0x8b--0x8d → result float 转换
d2i/d2l/d2f 0x8e--0x90 → result double 转换
i2b 0x91 value → result int → byte(截断第 2 章讲过)
i2c 0x92 → result int → char
i2s 0x93 → result int → short

i2b/i2c/i2s 也用于「窄化」------配合第 3 章 sspin(short 循环用 i2s 截断)理解。


6. 对象创建与访问(Object Creation / Access)

指令 opcode 格式 操作数栈 说明
new 0xbb index(2B) → objectref 创建对象,未初始化 (第 3 章:new+dup+invokespecial <init>
newarray 0xbc atype count → arrayref 创建基本类型数组
anewarray 0xbd index(2B) count → arrayref 创建引用类型数组
multianewarray 0xc5 index,dimensions → arrayref 创建多维数组(第 3 章:3D 只定前两维)
getfield 0xb4 index(2B) objectref → value 取实例字段(解析 §5.4.3.2)
putfield 0xb5 index(2B) objectref, value → 写实例字段
getstatic 0xb2 index(2B) → value 取静态字段
putstatic 0xb3 index(2B) value → 写静态字段
arraylength 0xbe arrayref → length 取数组长度
instanceof 0xc1 index(2B) objectref → result 类型判断(1/0)
checkcast 0xc0 index(2B) objectref → objectref 类型强转(失败 ClassCastException

new 后对象处于「未初始化」状态,必须紧跟 invokespecial <init>(第 3 章详解)。字段/静态指令的 index 都指向常量池里的 Fieldref


7. 操作数栈操作(Operand Stack Manipulation)

不按类型,纯栈结构操作。long/double 占一项(两条槽)。

指令 opcode 操作数栈 说明
pop 0x57 value → 弹出栈顶 1 字宽
pop2 0x58 value2, value1 → 弹出 2 字宽(long/double)
dup 0x59 value → value, value 复制栈顶
dup_x1 0x5a value2, value1 → value1, value2, value1 复制并插入栈顶下 1 位
dup_x2 0x5b 复杂 复制插到栈顶下 2 位
dup2 / dup2_x1 / dup2_x2 0x5c--0x5e --- 复制 2 字宽(long/double)的变体
swap 0x5f value2, value1 → value1, value2 交换栈顶两字宽

new Object() 用的就是 dup------new 压入未初始化对象,再 dup 一份供 <init> 消费、留一份作结果。


8. 控制转移(Control Transfer)

条件跳转的「比较」直接在栈上做,结果为分支偏移量。

指令 opcode 操作数栈 说明
ifeq 0x99 value → value == 0 则跳转
ifne 0x9a value → value != 0 则跳转
iflt/ifge/ifgt/ifle 0x9b--0x9e value → 与 0 比较(< / ≥ / > / ≤)
if_icmpeq 0x9f v1, v2 → 两 int 相等则跳
if_icmpne/if_icmplt/if_icmpge/if_icmpgt/if_icmple 0xa0--0xa4 v1, v2 → 两 int 比较跳转
if_acmpeq/if_acmpne 0xa5/0xa6 v1, v2 → 两引用相等/不等则跳
ifnull/ifnonnull 0xc6/0xc7 value → 引用为 null/非 null 则跳
goto 0xa7 branch(2B) 无条件跳
goto_w 0xc8 branch(4B) 无条件跳(大范围)
tableswitch 0xaa index → 密集 switch(第 3 章讲过的「快表」)
lookupswitch 0xab index → 稀疏 switch(排序键值对)
jsr/jsr_w/ret 0xa8/0xc9/0xa9 --- 旧版 子例程(finally 用,已弃用,对应第 2 章 returnAddress

第 3 章的 while 循环就是「底部测试 + goto 跳过首轮」模式;switch 按密集度选 tableswitch/lookupswitch


9. 方法调用与返回(Method Invocation / Return)

指令 opcode 格式 操作数栈 说明
invokevirtual 0xb6 index(2B) objref, [args] → result 实例虚方法(按实际类型分派)
invokespecial 0xb7 index(2B) objref, [args] → result 父类方法/<init>/私有方法
invokestatic 0xb8 index(2B) [args] → result 静态方法
invokeinterface 0xb9 index,count,0 objref, [args] → result 接口方法
invokedynamic 0xba index,0,0 [args] → result 动态调用(lambda/引导方法,§5.4.3.6)
ireturn 0xac value → [empty] 返回 int
lreturn/freturn/dreturn 0xad--0xaf value → 返回 long/float/double
areturn 0xb0 value → 返回引用
return 0xb1 → [empty] 返回 void

四个 invoke* 的区别是第 3 章的重点:invokespecial<init>invokestatic 调静态、invokevirtual 调普通实例方法、invokeinterface 调接口。调用前参数(和 objref)须按描述符顺序在栈上排好。


10. 异常与同步(Throwing / Synchronization)

指令 opcode 操作数栈 说明
athrow 0xbf objectref → objectref 抛出异常(第 3 章:构造 Throwable 后 athrow
monitorenter 0xc2 objectref → 进入对象监视器(加锁)
monitorexit 0xc3 objectref → 退出对象监视器(解锁)

synchronized 块编译为 monitorenter + monitorexit,并用异常表保证异常时也解锁。athrow 的异常处理由 Code 里的异常表指引。


11. 其他

指令 opcode 说明
nop 0x00 空操作(占位/对齐)
wide 0xc4 扩展后续指令操作数为 2 字节

12. 一个综合例子:拆解 x = a + b

源码:

java 复制代码
void add() {
    int x = a + b;   // 假设 a, b 是 int 字段,x 是局部变量
}

javap -c 可能这样反汇编(概念示意):

复制代码
0: aload_0          // 压入 this(字段在对象上)
1: getfield   #2    // 取字段 a 压栈
4: aload_0          // 压入 this
5: getfield   #3    // 取字段 b 压栈
8: iadd            // 弹出 a,b,压入 a+b
9: istore_1        // 存到局部变量 x(槽 1)

对应 opcode 流:0x2a 0xb4 0x?? 0x2a 0xb4 0x?? 0x60 0x3c------0x2a=aload_00xb4=getfield0x60=iadd0x3c=istore_1


13. 动手:用 javap -c 把玩真实字节码

写个含循环、分支、异常的小方法:

java 复制代码
public class Bytecode {
    public static int sum(int n) {
        int s = 0;
        for (int i = 1; i <= n; i++) s += i;
        return s;
    }
}
bash 复制代码
javac Bytecode.java
javap -c Bytecode.class

小结

  • 指令 = 1 字节 opcode + 可选操作数,长度可变,操作数多从栈取;
  • 记法统一:mnemonic + opcode 十六进制 + 栈图 + 异常;
  • 按前缀分族(i/l/f/d/a + load/store/const/add...),同一类操作 opcode 连续编号;
  • long/double 占栈一项(2 字宽),iinc 是唯一原地改局部变量的算术指令;
  • new/dup/invokespecial <init> 是对象创建固定三件套。
相关推荐
jun_bai14 分钟前
Ecplise中发布项目到配置的tomcat中
java·服务器
南城以南溫暖如初14718 分钟前
AI 智能商城 APP 定制开发的核心需求
java·人工智能·spring boot·mysql·mybatis·需求分析
摇滚侠19 分钟前
《SpringBoot 3:入门与应用实战》第 9 章 使用 WebMvc 开发应用 阅读笔记 21
java·spring boot·笔记
Sam_Deep_Thinking26 分钟前
聊聊开闭原则,以及它在Spring里的样子
java·后端·spring·程序员·开闭原则
SimonKing39 分钟前
开源神器 Navop:数据库+SSH+终端+AI,一个应用全搞定
java·后端·程序员
爱码猿42 分钟前
SpringBoot+MybatisPlus动态数据源
java·spring boot·后端
小义_43 分钟前
JDK 深度解析
java·linux·开发语言·python·面试
奈斯先生Vector1 小时前
从“能调用”到“可运营”:AI Agent 进入多模型时代后的架构升级
java·javascript·数据库·人工智能·算法·架构·aigc
Json____1 小时前
宿舍安全卫生检查系统:Node 全栈开发实战
java·前端·数据库·毕业设计·课程设计·毕设·wwwoop.com