专栏原创出处:github-源笔记文件 (opens new window) ,github-源码 (opens new window),欢迎 Star,转载请附上原文出处链接和本声明。
Java JVM-虚拟机专栏系列笔记,系统性学习可访问个人复盘笔记-技术博客 Java JVM-虚拟机 (opens new window)
# 一、前言
Java 虚拟机的指令由一个字节长度的、代表着某种特定操作含义的数字(操作码)以及跟随其后的零至多个代表此操作所需的参数(操作数)构成。
如果不考虑异常处理的话,那 Java 虚拟机的解释器可以使用下面这段伪代码作为最基本的执行模型来理解,这个执行模型虽然很简单,但依然可以有效正确地工作:
do {
自动计算 PC 寄存器的值加 1;
根据 PC 寄存器指示的位置,从字节码流中取出操作码;
if (字节码存在操作数) 从字节码流中取出操作数;
执行操作码所定义的操作;
} while (字节码流长度 > 0);
本文最后收集了虚拟机指令表,使用时刻当做字典查阅。
# 二、字节码与数据类型
在 Java 虚拟机的指令集中,大多数指令都包含其操作所对应的数据类型信息。
举个例子,iload 指令用于从局部变量表中加载 int 型的数据到操作数栈中,而 fload 指令加载的则是 float 类型的数据。这两条指令的操作在虚拟机内部可能会是由同一段代码来实现的,但在 Class 文件中它们必须拥有各自独立的操作码。
1.操作码助记符:
对于大部分与数据类型相关的字节码指令,它们的操作码助记符中都有特殊的字符来表明专门为哪种数据类型服务:
- i 代表对 int 类型的数据操作
- l 代表 long
- s 代表 short
- b 代表 byte
- c 代表 char
- f 代表 float
- d 代表 double
- a 代表 reference
也有一些指令的助记符中没有明确指明操作类型的字母,例如 arraylength 指令,它没有代表数据类型的特殊字符,但操作数永远只能是一个数组类型的对象。还有另外一些指令,例如无条件跳转指令 goto 则是与数据类型无关的指令。
2.有限的类型指令:
因为 Java 虚拟机的操作码长度只有一字节,所以包含了数据类型的操作码就为指令集的设计带来了很大的压力:如果每一种与数据类型相关的指令都支持 Java 虚拟机所有运行时数据类型的话,那么指令的数量恐怕就会超出一字节所能表示的数量范围了。 因此,Java 虚拟机的指令集对于特定的操作只提供了有限的类型相关指令去支持它,换句话说,指令集将会被故意设计成非完全独立的。有一些单独的指令可以在必要的时候用来将一些不支持的类型转换为可被支持的类型。
大多数对于 boolean、byte、short 和 char 类型数据的操作,实际上都是使用相应的对 int 类型作为运算类型来进行的。
# 三、 常见字节码指令
# 1. 加载和存储指令
将一个局部变量加载到操作栈:iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n>
将一个数值从操作数栈存储到局部变量表:istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>
将一个常量加载到操作数栈:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>
扩充局部变量表的访问索引的指令:wide
iload_<n>
,它代表了 iload_0、iload_1、iload_2 和 iload_3 这几条指令
# 2. 运算指令
算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶。
大体上运算指令可以分为两种:对整型数据进行运算的指令与对浮点型数据进行运算的指令。整数与浮点数的算术指令在溢出和被零除的时候也有各自不同的行为表现。
是不存在直接支持 byte、short、char 和 boolean 类型的算术指令,对于上述几种数据的运算,应使用操作 int 类型的指令代替。
所有的算术指令包括:
- 加法指令:iadd、ladd、fadd、dadd
- 减法指令:isub、lsub、fsub、dsub
- 乘法指令:imul、lmul、fmul、dmul
- 除法指令:idiv、ldiv、fdiv、ddiv
- 求余指令:irem、lrem、frem、drem
- 取反指令:ineg、lneg、fneg、dneg
- 位移指令:ishl、ishr、iushr、lshl、lshr、lushr
- 按位或指令:ior、lor
- 按位与指令:iand、land
- 按位异或指令:ixor、lxor
- 局部变量自增指令:iinc
- 比较指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp
# 3. 类型转换指令
类型转换指令可以将两种不同的数值类型相互转换,这些转换操作一般用于实现用户代码中的显式类型转换操作。
1.宽化类型转换(即小范围类型向大范围类型的安全转换,Java 虚拟机直接):
- int 类型到 long、float 或者 double 类型
- long 类型到 float、double 类型
- float 类型到 double 类型
2.窄化类型转换,就必须显式地使用转换指令来完成, 这些转换指令包括 i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l 和 d2f。 窄化类型转换可能会导致转换结果产生不同的正负号、不同的数量级的情况,转换过程很可能会导致数值的精度丢失。
# 4. 对象创建与访问指令
虽然类实例和数组都是对象,但 Java 虚拟机对类实例和数组的创建与操作使用了不同的字节码指令。
对象创建后,就可以通过对象访问指令获取对象实例或者数组实例中的字段或者数组元素,这些指令包括:
- 创建类实例的指令:new
- 创建数组的指令:newarray、anewarray、multianewarray
- 访问类字段(static 变量)和实例字段(非 static 变量)的指令:getfield、putfield、getstatic、putstatic
- 把一个数组元素加载到操作数栈的指令:baload、caload、saload、iaload、laload、faload、daload、aaload
- 将一个操作数栈的值储存到数组元素中的指令:bastore、castore、sastore、iastore、fastore、dastore、aastore
- 取数组长度的指令:arraylength
- 检查类实例类型的指令:instanceof、checkcast
# 5. 操作数栈管理指令
如同操作一个普通数据结构中的堆栈那样,Java 虚拟机提供了一些用于直接操作操作数栈的指令,包括:
- 将操作数栈的栈顶一个或两个元素出栈:pop、pop2
- 复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2
- 将栈最顶端的两个数值互换:swap
# 6. 控制转移指令
控制转移指令可以让 Java 虚拟机有条件或无条件地从指定位置指令(而不是控制转移指令)的下一条指令继续执行程序,从概念模型上理解,可以认为控制指令就是在有条件或无条件地修改 PC 寄存器的值。
控制转移指令包括:
- 条件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、if_icmpgt、if_icmple、if_icmpge、if_acmpeq 和 if_acmpne
- 复合条件分支:tableswitch、lookupswitch
- 无条件分支:goto、goto_w、jsr、jsr_w、ret
# 7. 方法调用和返回指令
- invokevirtual 指令:用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是 Java 语言中最常见的方法分派方式。
- invokeinterface 指令:用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。
- invokespecial 指令:用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法。
- invokestatic 指令:用于调用类静态方法(static 方法)。
- invokedynamic 指令:用于在运行时动态解析出调用点限定符所引用的方法。并执行该方法。前面四条调用指令的分派逻辑都固化在 Java 虚拟机内部,用户无法改变,而 invokedynamic 指令的分派逻辑是由用户所设定的引导方法决定的。
方法调用指令与数据类型无关,而方法返回指令是根据返回值的类型区分的,包括 ireturn(当返回值是 boolean、byte、char、short 和 int 类型时使用)、lreturn、freturn、dreturn 和 areturn,另外还有一条 return 指令供声明为 void 的方法、实例初始化方法、类和接口的类初始化方法使用。
# 8. 异常处理指令
在 Java 程序中显式抛出异常的操作(throw 语句)都由 athrow 指令来实现,除了用 throw 语句显式抛出异常的情况之外,《Java 虚拟机规范》还规定了许多运行时异常会在其他 Java 虚拟机指令检测到异常状况时自动抛出。
例如前面介绍整数运算中,当除数为零时,虚拟机会在 idiv 或 ldiv 指令中抛出 ArithmeticException 异常。
# 9. 同步指令
同步一段指令集序列通常是由 Java 语言中的 synchronized 语句块来表示的,Java 虚拟机的指令集中有 monitorenter 和 monitorexit 两条指令来支持 synchronized 关键字的语义,正确实现 synchronized 关键字需要 Javac 编译器与 Java 虚拟机两者共同协作支持
为了保证在方法异常完成时 monitorenter 和 monitorexit 指令依然可以正确配对执行,编译器会自动产生一个异常处理程序,这个异常处理程序声明可处理所有的异常,它的目的就是用来执行 monitorexit 指令。
# 四、字节码指令实战
限于篇幅,可直接参考本专栏内容 Java JVM 运行时栈帧结构、字节码分析实战 (opens new window)
# 五、字节码指令表
字节码 | 助记符 | 指令含义 |
---|---|---|
0x00 | nop | None |
0x01 | aconst_null | 将 null 推送至栈顶 |
0x02 | iconst_m1 | 将 int 型-1 推送至栈顶 |
0x03 | iconst_0 | 将 int 型 0 推送至栈顶 |
0x04 | iconst_1 | 将 int 型 1 推送至栈顶 |
0x05 | iconst_2 | 将 int 型 2 推送至栈顶 |
0x06 | iconst_3 | 将 int 型 3 推送至栈顶 |
0x07 | iconst_4 | 将 int 型 4 推送至栈顶 |
0x08 | iconst_5 | 将 int 型 5 推送至栈顶 |
0x09 | lconst_0 | 将 long 型 0 推送至栈顶 |
0x0a | lconst_1 | 将 long 型 1 推送至栈顶 |
0x0b | fconst_0 | 将 float 型 0 推送至栈顶 |
0x0c | fconst_1 | 将 float 型 1 推送至栈顶 |
0x0d | fconst_2 | 将 float 型 2 推送至栈顶 |
0x0e | dconst_0 | 将 double 型 0 推送至栈顶 |
0x0f | dconst_1 | 将 double 型 1 推送至栈顶 |
0x10 | bipush | 将单字节的常量值 (-128~127) 推送至栈顶 |
0x11 | sipush | 将一个短整型常量 (-32768~32767) 推送至栈顶 |
0x12 | ldc | 将 int,float 或 String 型常量值从常量池中推送至栈顶 |
0x13 | ldc_w | 将 int,float 或 String 型常量值从常量池中推送至栈顶 (宽索引) |
0x14 | ldc2_w | 将 long 或 double 型常量值从常量池中推送至栈顶 (宽索引) |
0x15 | iload | 将指定的 int 型本地变量推送至栈顶 |
0x16 | lload | 将指定的 long 型本地变量推送至栈顶 |
0x17 | fload | 将指定的 float 型本地变量推送至栈顶 |
0x18 | dload | 将指定的 double 型本地变量推送至栈顶 |
0x19 | aload | 将指定的引用类型本地变量推送至栈顶 |
0x1a | iload_0 | 将第一个 int 型本地变量推送至栈顶 |
0x1b | iload_1 | 将第二个 int 型本地变量推送至栈顶 |
0x1c | iload_2 | 将第三个 int 型本地变量推送至栈顶 |
0x1d | iload_3 | 将第四个 int 型本地变量推送至栈顶 |
0x1e | lload_0 | 将第一个 long 型本地变量推送至栈顶 |
0x1f | lload_1 | 将第二个 long 型本地变量推送至栈顶 |
0x20 | lload_2 | 将第三个 long 型本地变量推送至栈顶 |
0x21 | lload_3 | 将第四个 long 型本地变量推送至栈顶 |
0x22 | fload_0 | 将第一个 float 型本地变量推送至栈顶 |
0x23 | fload_1 | 将第二个 float 型本地变量推送至栈顶 |
0x24 | fload_2 | 将第三个 float 型本地变量推送至栈顶 |
0x25 | fload_3 | 将第四个 float 型本地变量推送至栈顶 |
0x26 | dload_0 | 将第一个 double 型本地变量推送至栈顶 |
0x27 | dload_1 | 将第二个 double 型本地变量推送至栈顶 |
0x28 | dload_2 | 将第三个 double 型本地变量推送至栈顶 |
0x29 | dload_3 | 将第四个 double 型本地变量推送至栈顶 |
0x2a | aload_0 | 将第一个引用类型本地变量推送至栈顶 |
0x2b | aload_1 | 将第二个引用类型本地变量推送至栈顶 |
0x2c | aload_2 | 将第三个引用类型本地变量推送至栈顶 |
0x2d | aload_3 | 将第四个引用类型本地变量推送至栈顶 |
0x2e | iaload | 将 int 型数组指定索引的值推送至栈顶 |
0x2f | laload | 将 long 型数组指定索引的值推送至栈顶 |
0x30 | faload | 将 float 型数组指定索引的值推送至栈顶 |
0x31 | daload | 将 double 型数组指定索引的值推送至栈顶 |
0x32 | aaload | 将引用类型数组指定索引的值推送至栈顶 |
0x33 | baload | 将 boolean 或 byte 型数组指定索引的值推送至栈顶 |
0x34 | caload | 将 char 型数组指定索引的值推送至栈顶 |
0x35 | saload | 将 short 型数组指定索引的值推送至栈顶 |
0x36 | istore | 将栈顶 int 型数值存入指定本地变量 |
0x37 | lstore | 将栈顶 long 型数值存入指定本地变量 |
0x38 | fstore | 将栈顶 float 型数值存入指定本地变量 |
0x39 | dstore | 将栈顶 double 型数值存入指定本地变量 |
0x3a | astore | 将栈顶引用类型数值存入指定本地变量 |
0x3b | istore_0 | 将栈顶 int 型数值存入第一个本地变量 |
0x3c | istore_1 | 将栈顶 int 型数值存入第二个本地变量 |
0x3d | istore_2 | 将栈顶 int 型数值存入第三个本地变量 |
0x3e | istore_3 | 将栈顶 int 型数值存入第四个本地变量 |
0x3f | lstore_0 | 将栈顶 long 型数值存入第一个本地变量 |
0x40 | lstore_1 | 将栈顶 long 型数值存入第二个本地变量 |
0x41 | lstore_2 | 将栈顶 long 型数值存入第三个本地变量 |
0x42 | lstore_3 | 将栈顶 long 型数值存入第四个本地变量 |
0x43 | fstore_0 | 将栈顶 float 型数值存入第一个本地变量 |
0x44 | fstore_1 | 将栈顶 float 型数值存入第二个本地变量 |
0x45 | fstore_2 | 将栈顶 float 型数值存入第三个本地变量 |
0x46 | fstore_3 | 将栈顶 float 型数值存入第四个本地变量 |
0x47 | dstore_0 | 将栈顶 double 型数值存入第一个本地变量 |
0x48 | dstore_1 | 将栈顶 double 型数值存入第二个本地变量 |
0x49 | dstore_2 | 将栈顶 double 型数值存入第三个本地变量 |
0x4a | dstore_3 | 将栈顶 double 型数值存入第四个本地变量 |
0x4b | astore_0 | 将栈顶引用型数值存入第一个本地变量 |
0x4c | astore_1 | 将栈顶引用型数值存入第二个本地变量 |
0x4d | astore_2 | 将栈顶引用型数值存入第三个本地变量 |
0x4e | astore_3 | 将栈顶引用型数值存入第四个本地变量 |
0x4f | iastore | 将栈顶 int 型数值存入指定数组的指定索引位置 |
0x50 | lastore | 将栈顶 long 型数值存入指定数组的指定索引位置 |
0x51 | fastore | 将栈顶 float 型数值存入指定数组的指定索引位置 |
0x52 | dastore | 将栈顶 double 型数值存入指定数组的指定索引位置 |
0x53 | aastore | 将栈顶引用型数值存入指定数组的指定索引位置 |
0x54 | bastore | 将栈顶 boolean 或 byte 型数值存入指定数组的指定索引位置 |
0x55 | castore | 将栈顶 char 型数值存入指定数组的指定索引位置 |
0x56 | sastore | 将栈顶 short 型数值存入指定数组的指定索引位置 |
0x57 | pop | 将栈顶数值弹出 (数值不能是 long 或 double 类型的) |
0x58 | pop2 | 将栈顶的一个 (对于非 long 或 double 类型) 或两个数值 (对于非 long 或 double 的其他类型) 弹出 |
0x59 | dup | 复制栈顶数值并将复制值压入栈顶 |
0x5a | dup_x1 | 复制栈顶数值并将两个复制值压入栈顶 |
0x5b | dup_x2 | 复制栈顶数值并将三个 (或两个) 复制值压入栈顶 |
0x5c | dup2 | 复制栈顶一个 (对于 long 或 double 类型) 或两个 (对于非 long 或 double 的其他类型) 数值并将复制值压入栈顶 |
0x5d | dup2_x1 | dup_x1 指令的双倍版本 |
0x5e | dup2_x2 | dup_x2 指令的双倍版本 |
0x5f | swap | 将栈顶最顶端的两个数值互换 (数值不能是 long 或 double 类型) |
0x60 | iadd | 将栈顶两 int 型数值相加并将结果压入栈顶 |
0x61 | ladd | 将栈顶两 long 型数值相加并将结果压入栈顶 |
0x62 | fadd | 将栈顶两 float 型数值相加并将结果压入栈顶 |
0x63 | dadd | 将栈顶两 double 型数值相加并将结果压入栈顶 |
0x64 | isub | 将栈顶两 int 型数值相减并将结果压入栈顶 |
0x65 | lsub | 将栈顶两 long 型数值相减并将结果压入栈顶 |
0x66 | fsub | 将栈顶两 float 型数值相减并将结果压入栈顶 |
0x67 | dsub | 将栈顶两 double 型数值相减并将结果压入栈顶 |
0x68 | imul | 将栈顶两 int 型数值相乘并将结果压入栈顶 |
0x69 | lmul | 将栈顶两 long 型数值相乘并将结果压入栈顶 |
0x6a | fmul | 将栈顶两 float 型数值相乘并将结果压入栈顶 |
0x6b | dmul | 将栈顶两 double 型数值相乘并将结果压入栈顶 |
0x6c | idiv | 将栈顶两 int 型数值相除并将结果压入栈顶 |
0x6d | ldiv | 将栈顶两 long 型数值相除并将结果压入栈顶 |
0x6e | fdiv | 将栈顶两 float 型数值相除并将结果压入栈顶 |
0x6f | ddiv | 将栈顶两 double 型数值相除并将结果压入栈顶 |
0x70 | irem | 将栈顶两 int 型数值作取模运算并将结果压入栈顶 |
0x71 | lrem | 将栈顶两 long 型数值作取模运算并将结果压入栈顶 |
0x72 | frem | 将栈顶两 float 型数值作取模运算并将结果压入栈顶 |
0x73 | drem | 将栈顶两 double 型数值作取模运算并将结果压入栈顶 |
0x74 | ineg | 将栈顶 int 型数值取负并将结果压入栈顶 |
0x75 | lneg | 将栈顶 long 型数值取负并将结果压入栈顶 |
0x76 | fneg | 将栈顶 float 型数值取负并将结果压入栈顶 |
0x77 | dneg | 将栈顶 double 型数值取负并将结果压入栈顶 |
0x78 | ishl | 将 int 型数值左移指定位数并将结果压入栈顶 |
0x79 | lshl | 将 long 型数值左移指定位数并将结果压入栈顶 |
0x7a | ishr | 将 int 型数值右 (带符号) 移指定位数并将结果压入栈顶 |
0x7b | lshr | 将 long 型数值右 (带符号) 移指定位数并将结果压入栈顶 |
0x7c | iushr | 将 int 型数值右 (无符号) 移指定位数并将结果压入栈顶 |
0x7d | lushr | 将 long 型数值右 (无符号) 移指定位数并将结果压入栈顶 |
0x7e | iand | 将栈顶两 int 型数值"按位与"并将结果压入栈顶 |
0x7f | land | 将栈顶两 long 型数值"按位与"并将结果压入栈顶 |
0x80 | ior | 将栈顶两 int 型数值"按位或"并将结果压入栈顶 |
0x81 | lor | 将栈顶两 long 型数值"按位或"并将结果压入栈顶 |
0x82 | ixor | 将栈顶两 int 型数值"按位异或"并将结果压入栈顶 |
0x83 | lxor | 将栈顶两 long 型数值"按位异或"并将结果压入栈顶 |
0x84 | iinc | 将指定 int 型变量增加指定值 (如 i++, i–, i+=2 等) |
0x85 | i2l | 将栈顶 int 型数值强制转换为 long 型数值并将结果压入栈顶 |
0x86 | i2f | 将栈顶 int 型数值强制转换为 float 型数值并将结果压入栈顶 |
0x87 | i2d | 将栈顶 int 型数值强制转换为 double 型数值并将结果压入栈顶 |
0x88 | l2i | 将栈顶 long 型数值强制转换为 int 型数值并将结果压入栈顶 |
0x89 | l2f | 将栈顶 long 型数值强制转换为 float 型数值并将结果压入栈顶 |
0x8a | l2d | 将栈顶 long 型数值强制转换为 double 型数值并将结果压入栈顶 |
0x8b | f2i | 将栈顶 float 型数值强制转换为 int 型数值并将结果压入栈顶 |
0x8c | f2l | 将栈顶 float 型数值强制转换为 long 型数值并将结果压入栈顶 |
0x8d | f2d | 将栈顶 float 型数值强制转换为 double 型数值并将结果压入栈顶 |
0x8e | d2i | 将栈顶 double 型数值强制转换为 int 型数值并将结果压入栈顶 |
0x8f | d2l | 将栈顶 double 型数值强制转换为 long 型数值并将结果压入栈顶 |
0x90 | d2f | 将栈顶 double 型数值强制转换为 float 型数值并将结果压入栈顶 |
0x91 | i2b | 将栈顶 int 型数值强制转换为 byte 型数值并将结果压入栈顶 |
0x92 | i2c | 将栈顶 int 型数值强制转换为 char 型数值并将结果压入栈顶 |
0x93 | i2s | 将栈顶 int 型数值强制转换为 short 型数值并将结果压入栈顶 |
0x94 | lcmp | 比较栈顶两 long 型数值大小, 并将结果 (1, 0 或-1) 压入栈顶 |
0x95 | fcmpl | 比较栈顶两 float 型数值大小, 并将结果 (1, 0 或-1) 压入栈顶;当其中一个数值为 NaN 时, 将-1 压入栈顶 |
0x96 | fcmpg | 比较栈顶两 float 型数值大小, 并将结果 (1, 0 或-1) 压入栈顶;当其中一个数值为 NaN 时, 将 1 压入栈顶 |
0x97 | dcmpl | 比较栈顶两 double 型数值大小, 并将结果 (1, 0 或-1) 压入栈顶;当其中一个数值为 NaN 时, 将-1 压入栈顶 |
0x98 | dcmpg | 比较栈顶两 double 型数值大小, 并将结果 (1, 0 或-1) 压入栈顶;当其中一个数值为 NaN 时, 将 1 压入栈顶 |
0x99 | ifeq | 当栈顶 int 型数值等于 0 时跳转 |
0x9a | ifne | 当栈顶 int 型数值不等于 0 时跳转 |
0x9b | iflt | 当栈顶 int 型数值小于 0 时跳转 |
0x9c | ifge | 当栈顶 int 型数值大于等于 0 时跳转 |
0x9d | ifgt | 当栈顶 int 型数值大于 0 时跳转 |
0x9e | ifle | 当栈顶 int 型数值小于等于 0 时跳转 |
0x9f | if_icmpeq | 比较栈顶两 int 型数值大小, 当结果等于 0 时跳转 |
0xa0 | if_icmpne | 比较栈顶两 int 型数值大小, 当结果不等于 0 时跳转 |
0xa1 | if_icmplt | 比较栈顶两 int 型数值大小, 当结果小于 0 时跳转 |
0xa2 | if_icmpge | 比较栈顶两 int 型数值大小, 当结果大于等于 0 时跳转 |
0xa3 | if_icmpgt | 比较栈顶两 int 型数值大小, 当结果大于 0 时跳转 |
0xa4 | if_icmple | 比较栈顶两 int 型数值大小, 当结果小于等于 0 时跳转 |
0xa5 | if_acmpeq | 比较栈顶两引用型数值, 当结果相等时跳转 |
0xa6 | if_acmpne | 比较栈顶两引用型数值, 当结果不相等时跳转 |
0xa7 | goto | 无条件跳转 |
0xa8 | jsr | 跳转至指定的 16 位 offset 位置, 并将 jsr 的下一条指令地址压入栈顶 |
0xa9 | ret | 返回至本地变量指定的 index 的指令位置 (一般与 jsr 或 jsr_w 联合使用) |
0xaa | tableswitch | 用于 switch 条件跳转, case 值连续 (可变长度指令) |
0xab | lookupswitch | 用于 switch 条件跳转, case 值不连续 (可变长度指令) |
0xac | ireturn | 从当前方法返回 int |
0xad | lreturn | 从当前方法返回 long |
0xae | freturn | 从当前方法返回 float |
0xaf | dreturn | 从当前方法返回 double |
0xb0 | areturn | 从当前方法返回对象引用 |
0xb1 | return | 从当前方法返回 void |
0xb2 | getstatic | 获取指定类的静态域, 并将其压入栈顶 |
0xb3 | putstatic | 为指定类的静态域赋值 |
0xb4 | getfield | 获取指定类的实例域, 并将其压入栈顶 |
0xb5 | putfield | 为指定类的实例域赋值 |
0xb6 | invokevirtual | 调用实例方法 |
0xb7 | invokespecial | 调用超类构建方法, 实例初始化方法, 私有方法 |
0xb8 | invokestatic | 调用静态方法 |
0xb9 | invokeinterface | 调用接口方法 |
0xba | invokedynamic | 调用动态方法 |
0xbb | new | 创建一个对象, 并将其引用引用值压入栈顶 |
0xbc | newarray | 创建一个指定的原始类型 (如 int, float, char 等) 的数组,并将其引用值压入栈顶 |
0xbd | anewarray | 创建一个引用型 (如类, 接口, 数组) 的数组, 并将其引用值压入栈顶 |
0xbe | arraylength | 获取数组的长度值并压入栈顶 |
0xbf | athrow | 将栈顶的异常抛出 |
0xc0 | checkcast | 检验类型转换, 检验未通过将抛出 ClassCastException |
0xc1 | instanceof | 检验对象是否是指定类的实际, 如果是将 1 压入栈顶, 否则将 0 压入栈顶 |
0xc2 | monitorenter | 获得对象的锁, 用于同步方法或同步块 |
0xc3 | monitorexit | 释放对象的锁, 用于同步方法或同步块 |
0xc4 | wide | 扩展本地变量的宽度 |
0xc5 | multianewarray | 创建指定类型和指定维度的多维数组 (执行该指令时,操作栈中必须包含各维度的长度值), 并将其引用压入栈顶 |
0xc6 | ifnull | 为 null 时跳转 |
0xc7 | ifnonnull | 不为 null 时跳转 |
0xc8 | goto_w | 无条件跳转 (宽索引) |
0xc9 | jsr_w | 跳转至指定的 32 位 offset 位置, 并将 jsr_w 的下一条指令地址压入栈顶 |
# 参考
- 《深入理解 Java 虚拟机:JVM 高级特性与最佳实践(第 3 版)》周志明 著