关于指令集的官方文档(JAVA8)
关于指令集的详细使用方式请参阅:
oracle官方指令集详细文档(JAVA8)
load
指令加载到操作数栈,操作数的大小根据具体指令来决定操作码 : 助记符 [操作数]
局部变量表
(todo)和操作数栈
(todo)之间来回传输一个
局部变量到操作数栈指令0x15 : iload [index]
: index是一个字节(8位),表示了局部变量表的索引,将指定的int型本地变量推送至栈顶0x16 : lload [index]
: index是一个字节的操作数(8位),表示了局部变量表的索引,将指定的long型本地变量推送至栈顶, 局部变量表中index和index+1的两个32位数构成了一个long型的数0x17 : fload [index]
: index是一个字节的操作数(8位),表示了局部变量表的索引,将指定的float型本地变量推送至栈顶,0x18 : dload [index]
: index是一个index是一个字节的操作数(8位),表示了局部变量表的索引,将指定的double型本地变量推送至栈顶, 局部变量表中index和index+1的两个32位数构成了一个long型的数0x19 : aload [index]
: index是一个字节的操作数(8位),表示了局部变量表的索引,将指定的引用类型本地变量推送至栈顶0x1a : iload_0
不需要操作数,将局部变量表索引值为0的int变量(32位)推送至栈顶0x1b : iload_1
: 不需要显示的操作数,将局部变量表索引值为1的int变量(32位)推送至栈顶0x1c : iload_2
: 不需要操作数,将局部变量表索引值为2的int变量(32位)推送至栈顶0x1d : iload_3
: 不需要操作数,将局部变量表索引值为3的int变量(32位)推送至栈顶0x1e : lload_0
: 不需要操作数,将局部变量表索引值为0的32位和索引值为1的32位,组成一个64位的long值,推送至栈顶0x1f : lload_1
: 不需要操作数,将局部变量表索引值为1的32位和索引值为2的32位,组成一个64位的long值,推送至栈顶0x20 : lload_2
: 不需要操作数,将局部变量表索引值为2的32位和索引值为3的32位,组成一个64位的long值,推送至栈顶0x21 : lload_3
: 不需要操作数,将局部变量表索引值为3的32位和索引值为4的32位,组成一个64位的long值,推送至栈顶0x22 : fload_0
: 与iload_
类同0x23 : fload_1
: 与iload_
类同0x24 : fload_2
: 与iload_
类同0x25 : fload_3
: 与iload_
类同0x26 : dload_0
: 与lload_0
类同0x27 : dload_1
: 与lload_0
类同0x28 : dload_2
: 与lload_0
类同0x29 : dload_3
: 与lload_0
类同0x2a : aload_0
: 与iload_
类同0x2b : aload_1
: 与iload_
类同0x2c : aload_2
: 与iload_
类同0x2d : aload_3
: 与iload_
类同一个
数值从操作数栈存储到局部变量表指令0x36 : istore index
: index为8位的一个字节,将栈顶int型数值存入局部变量表中索引值为index的32bit位置中0x37 : lstore [index]
: 一个操作数,将栈顶long型数值存入局部变量表索引值为 index和index+1 的两个32bit的位置中0x38 : fstore [index]
: index为8位的一个操作数,将栈顶float型数值存入局部变量表中索引值为index的32bit位置中0x39 : dstore [index]
: 一个操作数,将栈顶double型数值存入局部变量表索引值为 index和index+1 的两个32bit的位置中0x3a : astore [index]
一个操作数,将栈顶引用型数值存入指定本地变量0x3b : istore_0
: 不需要操作数,将栈顶int型数值存入第一个本地变量0x3c : istore_1
: 不需要操作数,将栈顶int型数值存入第二个本地变量0x3d : istore_2
: 不需要操作数,将栈顶int型数值存入第三个本地变量0x3e : istore_3
: 不需要操作数,将栈顶int型数值存入第四个本地变量0x3f : lstore_0
: 与istore_
类同0x40 : lstore_1
: 与istore_
类同0x41 : lstore_2
: 与istore_
类同0x42 : lstore_3
: 与istore_
类同0x43 : fstore_0
: 与istore_
类同0x44 : fstore_1
: 与istore_
类同0x45 : fstore_2
: 与istore_
类同0x46 : fstore_3
: 与istore_
类同0x47 : dstore_0
: 与istore_
类同0x48 : dstore_1
: 与istore_
类同0x49 : dstore_2
: 与istore_
类同0x4a : dstore_3
: 与istore_
类同0x4b : astore_0
: 与istore_
类同0x4c : astore_1
: 与istore_
类同0x4d : astore_2
: 与istore_
类同0x4e : astore_3
: 与istore_
类同一个
常量加载到操作数栈0x10 : bipush [byte]
: byte为单字节(8位)要操作的常量值,将单字节的常量值(-128~127)推送至栈顶,这个数在栈顶会扩展为32位的一个int数0x11 : sipush [byte1] [byte2]
: 操作数为2个字节(2个byte,一共16位),将两个字节组成一个短整型常量值(-32768~32767)推送至栈顶,这个数在栈顶会扩展为32位的一个int数, value = (byte1<<8)|byte2
0x12 : ldc [index]
: index为当前类常量池
的索引(预备知识: class的结构), 指向的常量必须是个int或float或指向类/方法类型/方法句柄的符号引用, 将其推送至栈顶0x13 : ldc_w [indexbyte1] [indexbyte2]
: 和ldc
差不多,只不过这个索引值是(indexbyte1<<8) | indexbyte2
0x14 : ldc2_w [indexbyte1] [indexbyte2]
: 和ldc_w
差不多,只不过指向的常量是个long或者double0x1 : aconst_null
: 无操作数将一个null值入栈到操作数栈中0x2 : iconst_m1
: 将int类型的常量 -1 入栈到操作数栈中iconst_
: 将int类型的常量{i | i=0,i=1,i=2,i=3,i=4,i=5}
入栈到操作数栈中, 0x3:iconst_0
,0x4:iconst_1
,0x5:iconst_2
,0x6:iconst_3
,0x7:iconst_4
,0x8:iconst_5
0x11:fconst_0
,0x12:fconst_1
,0x13:fconst_2
: 将float类型的常量,0.0f,1.0f,2.0f入栈到操作数栈中0x14:dconst_0,0x15:dconst_1
: 将double类型的常量 0.0d和1.0d入栈到操作数栈中0xc4 : wide
wide
指令之后紧跟着被扩展的指令wide [indexbyte1] [indexbyte2]
opcode ∈ {iload, fload, aload, lload, dload, istore, fstore, astore, lstore, dstore, ret}
为什么又有一个wide指令?
methods.attributes.LocalVariableTable.local_variable_table_length
这个值是个u2
类型=16bit的值,取值范围可以使0~65535,也就是说局部变量表的索引值最大可以是65536iload
指令正常后面跟随一个indexbyte1
来表明操作的局部变量表的索引值indexbyte1
是一个无符号的索引值,取值范围是 0~255,wide
指令就有用了,他可以帮助iload
指令去操作局部变量表中索引值超过255以上的值wide iinc [indexbyte1] [indexbyte2] [constbyte1] [constbyte2]
(indexbyte1<<8)|indexbyte2
的值做自增操作(constbyte1<<8)|constbyte2
Arithmetic Instructions
算术指令用于对两个操作数栈上的值进行某种特定运算, 并把结果重新存入到操作栈顶
大体上运算指令可以分为两种:
整数与浮点数的算术指令在溢出和被零除的时候也有各自不同的行为表现
无论是哪种算术指令, 均是使用Java虚拟机的算术类型来进行计算的, 换句话说不存在直接支持byte、 short、 char和boolean类型的算术指令, 对于上述几种数据的运算, 应使用操作int类型的指令代替
0x60 : iadd
将栈顶两个int型数值相加出栈并将结果压入栈顶0x61 : ladd
将栈顶两个long型数值相加出栈并将结果压入栈顶0x62 : fadd
将栈顶两个float型数值相加出栈并将结果压入栈顶0x63 : dadd
将栈顶两个double型数值相加出栈并将结果压入栈顶0x64 : isub
将栈顶两个int型数值相减出栈并将结果压入栈顶0x65 : lsub
将栈顶两个long型数值相减出栈并将结果压入栈顶0x66 : fsub
将栈顶两个float型数值相减出栈并将结果压入栈顶0x67 : dsub
将栈顶两个double型数值相减出栈并将结果压入栈顶value2
是减数,value2
下面的value1
是被减数imul、 lmul、 fmul、 dmul
idiv、 ldiv、 fdiv、 ddiv
result = value1 / value2
irem、 lrem、 frem、 drem
result = value1 - (value1/value2) * value2
ineg、 lneg、 fneg、 dneg
ishl、 ishr、 iushr、 lshl、 lshr、 lushr
ior、 lor
iand、 land
ixor、 lxor
0x84 : iinc [index] [const]
对局部变量表索引为index(8bit)
的值,自增const(8bit)
大小dcmpg、 dcmpl、 fcmpg、 fcmpl、 lcmp
dcmp
fcmp
lcmp
Type Conversion Instructions
类型转换指令可以将两种不同的数值类型相互转换, 这些转换操作一般用于实现用户代码中的显式类型转换操作
或者用来处理本节开篇所提到的字节码指令集中数据类型相关指令无法与数据类型一一对应的问题。
Java虚拟机直接支持(即转换时无须显式的转换指令) 以下数值类型的宽化类型转换(Widening Numeric Conversion, 即小范围类型向大范围类型的安全转换) :
与之相对的, 处理窄化类型转换(Narrowing Numeric Conversion) 时, 就必须显式地使用转换指令来完成,这些转换指令包括
0x91 : i2b
0x92 : i2c
0x93 : i2s
0x88 : l2i
0x8b : f2i
0x8c : f2l
0x8e : d2i
0x8f : d2l
0x90 : d2f
在将int或long类型窄化转换为整数类型T的时候, 转换过程仅仅是简单丢弃除最低位N字节以外的内容, N是类型T的数据类型长度, 这将可能导致转换结果与输入值有不同的正负号。 对于了解计算机数值存储和表示的程序员来说这点很容易理解, 因为原来符号位处于数值的最高位, 高位被丢弃之后, 转换结果的符号就取决于低N字节的首位了。
尽管数据类型窄化转换可能会发生上限溢出、 下限溢出和精度丢失等情况, 但是《Java虚拟机规范》 中明确规定数值类型的窄化转换指令永远不可能导致虚拟机抛出运行时异常。
Object Creation and Manipulation
虽然类实例和数组都是对象, 但Java虚拟机对类实例和数组的创建与操作使用了不同的字节码指令(在下一章会讲到数组和普通类的类型创建过程是不同的) 。 对象创建后, 就可以通过对象访问指令
获取对象实例或者数组实例中的字段或者数组元素, 这些指令包括:
0xbb : new [indexbyte1] [indexbyte2]
(indexbyte1<<8)|indexbyte2
应该是个类或者接口的符号引用0xbc : newarray [atype]
0xbd : anewarray [indexbyte1] [indexbyte2]
0xc5 : multianewarray [indexbyte1] [indexbyte2] [dimensions]
0xb2 : getstatic [indexbyte1] [indexbyte2]
: 以(indexbyte1<<8)|indexbyte2
为索引值 指向常量池中的静态变量的reference 将它的值放入操作数栈中0xb3 : putstatic [indexbyte1] [indexbyte2]
: 和上面的相反,将操作数栈的reference放入静态变量中0xb2 : getfield [indexbyte1] [indexbyte2]
getfeild
的时候,栈顶一定得是个对象的引用才合法(indexbyte1<<8)|indexbyte2
是这个对象的类的常量池中的索引,应该指向的是class文件中描述的fields
的索引, (就是特么的成员变量)0xb5 : putfield [indexbyte1] [indexbyte2]
objectref, value
(indexbyte1<<8)|indexbyte2
计算出objectref
的成员变量的索引value
的值赋给objectref
的成员变量objectref, value
出栈0x33 : baload
0x34 : caload
0x35 : saload
0x2e : iaload
0x2f : laload
0x30 : faload
0x31 : daload
0x32 : aaload
0x54 : bastore
0x55 : castore
0x56 : sastore
0x4f : iastore
0x51 : fastore
0x52 : dastore
0x53 : aastore
0xbe : arraylength
0xc1 : instanceof [indexbyte1] [indexbyte2]
: 判断对象是否为指定类型0xc0 : checkcast [indexbyte1] [indexbyte2]
: 检查对象是否符合给定的类型如同操作一个普通数据结构中的堆栈那样, Java虚拟机提供了一些用于直接操作操作数栈的指令
0x57 : pop
: 单纯的出栈1个元素,32bit0x58 : pop2
: 单纯的出栈2个元素,64bit0x59 : dup
: 复制栈顶数值(数值不能是long或double类型的)并将复制值压入栈顶0x5c : dup2
: 复制栈顶数值(数值不能是long或double类型的)并将两个复制值压入栈顶0x5a : dup_x1
: 复制栈顶数值(数值不能是long或double类型的)并将三个(或两个)复制值压入栈顶(0x5d : dup2_x1
: 复制栈顶一个(long或double类型的)或两个(其它)数值并将复制值压入栈顶0x5b : dup_x2
: 复制栈顶数值(long或double类型的)并将两个复制值压入栈顶0x5e : dup2_x2
: 复制栈顶数值(long或double类型的)并将三个(或两个)复制值压入栈顶0x5f : swap
: 将栈顶两个值互换控制转移指令可以让Java虚拟机有条件或无条件地从指定位置指令(而不是控制转移指令) 的下一条指令继续执行程序,
从概念模型上理解, 可以认为控制指令就是在有条件或无条件地修改PC寄存器的值
0x99 : ifeq [branchbyte1] [branchbyte2]
: 栈顶元素出栈,并判断当且仅当栈顶的数据=0时,比较结果是否为真,如果为真,跳转偏移量为(branchbyte1<<8)|branchbyte2
的指令位置0x9a : ifne [branchbyte1] [branchbyte2]
: 栈顶元素出栈,并判断当且仅当栈顶的数据≠0时,比较结果是否为真,如果为真,跳转偏移量为(branchbyte1<<8)|branchbyte2
的指令位置0x9b : iflt [branchbyte1] [branchbyte2]
: 栈顶元素出栈,并判断当且仅当栈顶的数据<0时,比较结果是否为真,如果为真,跳转偏移量为(branchbyte1<<8)|branchbyte2
的指令位置0x9c : ifge [branchbyte1] [branchbyte2]
: 栈顶元素出栈,并判断当且仅当栈顶的数据>=0时,比较结果是否为真,如果为真,跳转偏移量为(branchbyte1<<8)|branchbyte2
的指令位置0x9d : ifgt [branchbyte1] [branchbyte2]
: 栈顶元素出栈,并判断当且仅当栈顶的数>0时,比较结果是否为真,如果为真,跳转偏移量为(branchbyte1<<8)|branchbyte2
的指令位置0x9e : ifle [branchbyte1] [branchbyte2]
: 栈顶元素出栈,并判断当且仅当栈顶的数据<=时,比较结果是否为真,如果为真,跳转偏移量为(branchbyte1<<8)|branchbyte2
的指令位置0xc6 : ifnull [branchbyte1] [branchbyte2]
: 如果为空,跳转(branchbyte1<<8)|branchbyte2
指令位置0xc7 : ifnonnull [branchbyte1] [branchbyte2]
: 如果非空,跳转(branchbyte1<<8)|branchbyte2
指令位置0x9f : if_icmpeq
: 操作数栈2个32bit的int值比较,且出栈, 如果相等则为真, 则跳转偏移量为(branchbyte1<<8)|branchbyte2
的指令位置0xa0 : if_icmpne
: 类同0xa1 : if_icmplt
: 类同0xa2 : if_icmpge
: 类同0xa3 : if_icmpgt
: 类同0xa4 : if_icmple
: 类同0xa5 : if_acmpeq [branchbyte1] [branchbyte2]
: 操作数栈中两个reference类型的比较,如果相等,则为真,其他类同0xa6 : if_acmpne [branchbyte1] [branchbyte2]
: 操作数栈中两个reference类型的比较,如果不相等,则为真,其他类同0xaa : tableswitch
: 参见官方文档0xab : lookupswitch
: 参见官方文档0xa7 : goto [branchbyte1] [branchbyte2]
: 无条件跳转到(branchbyte1<<8)|branchbyte2
指令位置0xc8 : goto_w [branchbyte1] [branchbyte2] [branchbyte3] [branchbyte4]
: 无条件跳转到(branchbyte1<<24)|(branchbyte1<<16)|(branchbyte1<<8)|branchbyte4
指令位置, 虽然有4字节, 但是class文件的规范规定了方法的指令长度是个u2
数字,也就是说一个方法最多也就 65535 个指令, 这个指令并没什么卵用jsr
: 大神说JDK1.4.2开始就不用这个指令了jsr_w
: 大神说JDK1.4.2开始就不用这个指令了ret
: 大神说JDK1.4.2开始就不用这个指令了jsr
把returnAddress入栈之后,跳转finally代码块,finally代码块执行完用ret
回到jsr
之后的位置继续执行jsr
和ret
的功能,也就是字节码指令冗余了一些0xb6 : invokevirtual [indexbyte1] [indexbyte2]
: 调用实例方法,在class常量池中找方法(indexbyte1<<8)|indexbyte2
是索引值,同时操作数栈中的实例reference和参数都出栈传给被调用的方法0xb9 : invokeinterface [indexbyte1] [indexbyte2] [count] [0]
调用接口方法,同上0xb7 : invokespecial [indexbyte1] [indexbyte2]
: 调用实例方法,专门用来调用父类方法/私有方法/和实例初始化方法0xb8 : invokestatic [indexbyte1] [indexbyte2]
: 调用静态方法, 操作数栈中没有实例的reference了0xba : invokedynamic [indexbyte1] [indexbyte2] [0] [0]
: 调用动态方法, (indexbyte1<<8)|indexbyte2
指向的是CONSTANT_InvokeDynamic_info,而不是CONSTANT_Methodref_info了 参见invokedynamic指令0xac : ireturn
从操作数栈顶出栈,然后入栈到调用者栈帧的操作数栈中, 同时也将本操作数栈所有值丢弃0xad : lreturn
0xae : freturn
0xaf : dreturn
0xb0 : areturn
0xb1 : return
本栈帧的操作数栈所有值全部丢弃0xbf : athrow
操作数栈栈顶是个异常(Throwable)的reference,把这个异常reference出栈丢给异常处理器(Java里就是catch代码块)处理,如果没有那么该栈帧要出栈了这个指令一般是在使用 synchronized
关键字的时候出现, 需要了解对象头的概念
0xc2 : monitorenter
: 尝试获取操作数栈栈顶那个reference指向的对象的monitor(在对象头中), 文档0xc3 : monitorexit
: 退出一个对象的monitor