ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

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在排查诡异的StackOverflowError、VerifyError时直达字节码层。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次之执行后两者弹出、压入result。long/double在栈上算一项但占两个字宽。1.2 保留操作码与虚拟机错误保留操作码不出现在合法 class 文件里0xcabreakpoint、0xfeimpdep1、0xffimpdep2——仅供实现内部调试/扩展用。wide指令0xc4为操作数扩成 2 字节用于访问索引 255 的局部变量或常量池项。VirtualMachineError子类指令仅允许抛这些InternalError、OutOfMemoryError、StackOverflowError、UnknownError。2. 常量与压栈Constants把常量/引用压入操作数栈。小常量有「快车道」专用操作码第 3 章useManyNumeric示例里见过。指令opcode格式操作数栈说明aconst_null0x01—→ null压入 null 引用iconst_m10x02—→ -1int -1iconst_i0x03–0x08—→ i(i 0…5)int 0~5每值一个 opcodelconst_l0x09–0x0a—→ l(l 0,1)long 0/1fconst_f0x0b–0x0d—→ f(0/1/2)float 0/1/2dconst_d0x0e–0x0f—→ d(0/1)double 0/1bipush0x10byte→ value压入 1 字节有符号立即数sipush0x11short→ value压入 2 字节立即数ldc0x12index(1B)→ value从常量池#index加载int/float/String/类ldc_w0x13index(2B)→ value同ldc索引 2 字节ldc2_w0x14index(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格式操作数栈说明iload0x15index→ value加载 int 局部变量槽 indexiload_n0x1a–0x1d—→ value(n0…3)加载槽 0~3 的 int省 1 字节操作数lload0x16index→ value加载 long占 2 槽fload0x17index→ value加载 floatdload0x18index→ value加载 double占 2 槽aload0x19index→ value加载引用对象/数组istore0x36indexvalue →存 int 到局部变量istore_n0x3b–0x3e—value →(n0…3)存槽 0~3 的 intlstore/fstore/dstore/astore0x37/0x38/0x39/0x3aindexvalue →对应类型存储数组元素加载/存储下标在栈上指令opcode操作数栈说明iaload0x2earrayref, index → valueint 数组取元素iaload/faload/baload/caload/saload0x2e–0x35同构各基本类型数组取元素aaload0x32arrayref, index → value引用数组取元素§6.5 示例弹出引用数组与 int 索引取组件引用压栈iastore/fastore/bastore/castore/sastore/aastore0x4f–0x53arrayref, index, value →各类型数组存元素家族规律areference、iint、llong、ffloat、ddouble、bbyte/boolean、cchar、sshortload取、store存、aloadaload。4. 算术运算Arithmetic全部在操作数栈上完成。整数用补码、浮点用 IEEE 754第 2 章。指令opcode操作数栈说明iadd0x60value1, value2 → resultint 加ladd0x61value1, value2 → resultlong 加fadd0x62→ resultfloat 加dadd0x63→ resultdouble 加isub0x64value1, value2 → resultint 减value1 - value2imul0x68→ resultint 乘idiv0x6cvalue1, value2 → resultint 除抛ArithmeticException当除 0irem0x70→ resultint 取余ineg0x74value → resultint 取负ishl/ishr/iushr0x78/0x7a/0x7cvalue1, value2 → result左移/算术右移/逻辑右移iand/ior/ixor0x7e/0x80/0x82→ result按位与/或/异或对应l/f/d家族0x61~0x83同构long/float/double 算术idiv/ldiv抛ArithmeticException属于运行期检查异常第 2 章讲过。iinc见下是唯一个「直接修改局部变量、不进出栈」的算术指令。iinc0x84indexconst→ 把局部变量index的 int原地加 constvalue → valueconst。这是i的高效实现不碰操作数栈。5. 类型转换Type Conversion指令opcode操作数栈说明i2l0x85value → resultint → longi2f0x86→ resultint → floati2d0x87→ resultint → doublel2i0x88→ resultlong → int截断l2f/l2d0x89/0x8a→ resultlong → float/doublef2i/f2l/f2d0x8b–0x8d→ resultfloat 转换d2i/d2l/d2f0x8e–0x90→ resultdouble 转换i2b0x91value → resultint → byte截断第 2 章讲过i2c0x92→ resultint → chari2s0x93→ resultint → shorti2b/i2c/i2s也用于「窄化」——配合第 3 章sspinshort 循环用i2s截断理解。6. 对象创建与访问Object Creation / Access指令opcode格式操作数栈说明new0xbbindex(2B)→ objectref创建对象未初始化第 3 章newdupinvokespecial initnewarray0xbcatypecount → arrayref创建基本类型数组anewarray0xbdindex(2B)count → arrayref创建引用类型数组multianewarray0xc5index,dimensions→ arrayref创建多维数组第 3 章3D 只定前两维getfield0xb4index(2B)objectref → value取实例字段解析 §5.4.3.2putfield0xb5index(2B)objectref, value →写实例字段getstatic0xb2index(2B)→ value取静态字段putstatic0xb3index(2B)value →写静态字段arraylength0xbearrayref → length取数组长度instanceof0xc1index(2B)objectref → result类型判断1/0checkcast0xc0index(2B)objectref → objectref类型强转失败ClassCastExceptionnew后对象处于「未初始化」状态必须紧跟invokespecial init第 3 章详解。字段/静态指令的index都指向常量池里的Fieldref。7. 操作数栈操作Operand Stack Manipulation不按类型纯栈结构操作。long/double占一项两条槽。指令opcode操作数栈说明pop0x57value →弹出栈顶 1 字宽pop20x58value2, value1 →弹出 2 字宽long/doubledup0x59value → value, value复制栈顶dup_x10x5avalue2, value1 → value1, value2, value1复制并插入栈顶下 1 位dup_x20x5b复杂复制插到栈顶下 2 位dup2/dup2_x1/dup2_x20x5c–0x5e—复制 2 字宽long/double的变体swap0x5fvalue2, value1 → value1, value2交换栈顶两字宽new Object()用的就是dup——new压入未初始化对象再dup一份供init消费、留一份作结果。8. 控制转移Control Transfer条件跳转的「比较」直接在栈上做结果为分支偏移量。指令opcode操作数栈说明ifeq0x99value →value 0 则跳转ifne0x9avalue →value ! 0 则跳转iflt/ifge/ifgt/ifle0x9b–0x9evalue →与 0 比较 / ≥ / / ≤if_icmpeq0x9fv1, v2 →两 int 相等则跳if_icmpne/if_icmplt/if_icmpge/if_icmpgt/if_icmple0xa0–0xa4v1, v2 →两 int 比较跳转if_acmpeq/if_acmpne0xa5/0xa6v1, v2 →两引用相等/不等则跳ifnull/ifnonnull0xc6/0xc7value →引用为 null/非 null 则跳goto0xa7branch(2B)无条件跳goto_w0xc8branch(4B)无条件跳大范围tableswitch0xaaindex →密集switch第 3 章讲过的「快表」lookupswitch0xabindex →稀疏switch排序键值对jsr/jsr_w/ret0xa8/0xc9/0xa9—旧版子例程finally 用已弃用对应第 2 章returnAddress第 3 章的while循环就是「底部测试 goto跳过首轮」模式switch按密集度选tableswitch/lookupswitch。9. 方法调用与返回Method Invocation / Return指令opcode格式操作数栈说明invokevirtual0xb6index(2B)objref, [args] → result实例虚方法按实际类型分派invokespecial0xb7index(2B)objref, [args] → result父类方法/init/私有方法invokestatic0xb8index(2B)[args] → result静态方法invokeinterface0xb9index,count,0objref, [args] → result接口方法invokedynamic0xbaindex,0,0[args] → result动态调用lambda/引导方法§5.4.3.6ireturn0xacvalue → [empty]返回 intlreturn/freturn/dreturn0xad–0xafvalue →返回 long/float/doubleareturn0xb0value →返回引用return0xb1→ [empty]返回 void四个invoke*的区别是第 3 章的重点invokespecial调init、invokestatic调静态、invokevirtual调普通实例方法、invokeinterface调接口。调用前参数和objref须按描述符顺序在栈上排好。10. 异常与同步Throwing / Synchronization指令opcode操作数栈说明athrow0xbfobjectref → objectref抛出异常第 3 章构造 Throwable 后athrowmonitorenter0xc2objectref →进入对象监视器加锁monitorexit0xc3objectref →退出对象监视器解锁synchronized块编译为monitorentermonitorexit并用异常表保证异常时也解锁。athrow的异常处理由Code里的异常表指引。11. 其他指令opcode说明nop0x00空操作占位/对齐wide0xc4扩展后续指令操作数为 2 字节12. 一个综合例子拆解x a b源码voidadd(){intxab;// 假设 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压入 ab 9: istore_1 // 存到局部变量 x槽 1对应 opcode 流0x2a 0xb4 0x?? 0x2a 0xb4 0x?? 0x60 0x3c——0x2aaload_00xb4getfield0x60iadd0x3cistore_1。13. 动手用javap -c把玩真实字节码写个含循环、分支、异常的小方法publicclassBytecode{publicstaticintsum(intn){ints0;for(inti1;in;i)si;returns;}}javac Bytecode.java javap-cBytecode.class小结指令 1 字节 opcode 可选操作数长度可变操作数多从栈取记法统一mnemonic opcode 十六进制 栈图 异常按前缀分族i/l/f/d/aload/store/const/add...同一类操作 opcode 连续编号long/double占栈一项2 字宽iinc是唯一原地改局部变量的算术指令new/dup/invokespecial init是对象创建固定三件套。
返回列表