JVM虚拟机指令
参考及转载:
(1)https://blog.csdn.net/qq_33301113/article/details/73717855
(2)https://blog.csdn.net/wangxf_8341/article/details/50402525
(3)关于指令使用的例子 http://www.importnew.com/13107.html
字节码 | 助记符 | 指令含义 |
---|---|---|
0x00 | nop | 什么都不做 |
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 | 将do le型0推送至栈顶 |
0x0f | dconst_1 | 将do le型1推送至栈顶 |
0x10 | bipush | 将单字节的常量值(-128~127)推送至栈顶 |
0x11 | sipush | 将一个短整型常量值(-32768~32767)推送至栈顶 |
0x12 | ldc | 将int, float或String型常量值从常量池中推送至栈顶 |
0x13 | ldc_w | 将int, float或String型常量值从常量池中推送至栈顶(宽索引) |
0x14 | ldc2_w | 将long或do le型常量值从常量池中推送至栈顶(宽索引) |
0x15 | iload | 将指定的int型本地变量 |
0x16 | lload | 将指定的long型本地变量 |
0x17 | fload | 将指定的float型本地变量 |
0x18 | dload | 将指定的do le型本地变量 |
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 | 将第一个do le型本地变量 |
0x27 | dload_1 | 将第二个do le型本地变量 |
0x28 | dload_2 | 将第三个do le型本地变量 |
0x29 | dload_3 | 将第四个do le型本地变量 |
0x2a | aload_0 | 将第一个引用类型本地变量 |
0x2b | aload_1 | 将第二个引用类型本地变量 |
0x2c | aload_2 | 将第三个引用类型本地变量 |
0x2d | aload_3 | 将第四个引用类型本地变量 |
0x2e | iaload | 将int型数组指定索引的值推送至栈顶 |
0x2f | laload | 将long型数组指定索引的值推送至栈顶 |
0x30 | faload | 将float型数组指定索引的值推送至栈顶 |
0x31 | daload | 将do le型数组指定索引的值推送至栈顶 |
0x32 | aaload | 将引用型数组指定索引的值推送至栈顶 |
0x33 | baload | 将boolean或byte型数组指定索引的值推送至栈顶 |
0x34 | caload | 将char型数组指定索引的值推送至栈顶 |
0x35 | saload | 将short型数组指定索引的值推送至栈顶 |
0x36 | istore | 将栈顶int型数值存入指定本地变量 |
0x37 | lstore | 将栈顶long型数值存入指定本地变量 |
0x38 | fstore | 将栈顶float型数值存入指定本地变量 |
0x39 | dstore | 将栈顶do le型数值存入指定本地变量 |
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 | 将栈顶do le型数值存入第一个本地变量 |
0x48 | dstore_1 | 将栈顶do le型数值存入第二个本地变量 |
0x49 | dstore_2 | 将栈顶do le型数值存入第三个本地变量 |
0x4a | dstore_3 | 将栈顶do le型数值存入第四个本地变量 |
0x4b | astore_0 | 将栈顶引用型数值存入第一个本地变量 |
0x4c | astore_1 | 将栈顶引用型数值存入第二个本地变量 |
0x4d | astore_2 | 将栈顶引用型数值存入第三个本地变量 |
0x4e | astore_3 | 将栈顶引用型数值存入第四个本地变量 |
0x4f | iastore | 将栈顶int型数值存入指定数组的指定索引位置 |
0x50 | lastore | 将栈顶long型数值存入指定数组的指定索引位置 |
0x51 | fastore | 将栈顶float型数值存入指定数组的指定索引位置 |
0x52 | dastore | 将栈顶do le型数值存入指定数组的指定索引位置 |
0x53 | aastore | 将栈顶引用型数值存入指定数组的指定索引位置 |
0x54 | bastore | 将栈顶boolean或byte型数值存入指定数组的指定索引位置 |
0x55 | castore | 将栈顶char型数值存入指定数组的指定索引位置 |
0x56 | sastore | 将栈顶short型数值存入指定数组的指定索引位置 |
0x57 | pop | 将栈顶数值弹出 (数值不能是long或do le类型的) |
0x58 | pop2 | 将栈顶的一个(long或do le类型的)或两个数值弹出(其它) |
0x59 | dup | 复制栈顶数值并将复制值压入栈顶 |
0x5a | dup_x1 | 复制栈顶数值并将两个复制值压入栈顶 |
0x5b | dup_x2 | 复制栈顶数值并将三个(或两个)复制值压入栈顶 |
0x5c | dup2 | 复制栈顶一个(long或do le类型的)或两个(其它)数值并将复制值压入栈顶 |
0x5d | dup2_x1 | dup_x1 指令的双倍版本 |
0x5e | dup2_x2 | dup_x2 指令的双倍版本 |
0x5f | swap | 将栈最顶端的两个数值互换(数值不能是long或do le类型的) |
0x60 | iadd | 将栈顶两int型数值相加并将结果压入栈顶 |
0x61 | ladd | 将栈顶两long型数值相加并将结果压入栈顶 |
0x62 | fadd | 将栈顶两float型数值相加并将结果压入栈顶 |
0x63 | dadd | 将栈顶两do le型数值相加并将结果压入栈顶 |
0x64 | is | 将栈顶两int型数值相减并将结果压入栈顶 |
0x65 | ls | 将栈顶两long型数值相减并将结果压入栈顶 |
0x66 | fs | 将栈顶两float型数值相减并将结果压入栈顶 |
0x67 | ds | 将栈顶两do le型数值相减并将结果压入栈顶 |
0x68 | imul | 将栈顶两int型数值相乘并将结果压入栈顶 |
0x69 | lmul | 将栈顶两long型数值相乘并将结果压入栈顶 |
0x6a | fmul | 将栈顶两float型数值相乘并将结果压入栈顶 |
0x6b | dmul | 将栈顶两do le型数值相乘并将结果压入栈顶 |
0x6c | idiv | 将栈顶两int型数值相除并将结果压入栈顶 |
0x6d | ldiv | 将栈顶两long型数值相除并将结果压入栈顶 |
0x6e | fdiv | 将栈顶两float型数值相除并将结果压入栈顶 |
0x6f | ddiv | 将栈顶两do le型数值相除并将结果压入栈顶 |
0x70 | irem | 将栈顶两int型数值作取模运算并将结果压入栈顶 |
0x71 | lrem | 将栈顶两long型数值作取模运算并将结果压入栈顶 |
0x72 | frem | 将栈顶两float型数值作取模运算并将结果压入栈顶 |
0x73 | drem | 将栈顶两do le型数值作取模运算并将结果压入栈顶 |
0x74 | ineg | 将栈顶int型数值取负并将结果压入栈顶 |
0x75 | lneg | 将栈顶long型数值取负并将结果压入栈顶 |
0x76 | fneg | 将栈顶float型数值取负并将结果压入栈顶 |
0x77 | dneg | 将栈顶do le型数值取负并将结果压入栈顶 |
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型数值强制转换成do le型数值并将结果压入栈顶 |
0x88 | l2i | 将栈顶long型数值强制转换成int型数值并将结果压入栈顶 |
0x89 | l2f | 将栈顶long型数值强制转换成float型数值并将结果压入栈顶 |
0x8a | l2d | 将栈顶long型数值强制转换成do le型数值并将结果压入栈顶 |
0x8b | f2i | 将栈顶float型数值强制转换成int型数值并将结果压入栈顶 |
0x8c | f2l | 将栈顶float型数值强制转换成long型数值并将结果压入栈顶 |
0x8d | f2d | 将栈顶float型数值强制转换成do le型数值并将结果压入栈顶 |
0x8e | d2i | 将栈顶do le型数值强制转换成int型数值并将结果压入栈顶 |
0x8f | d2l | 将栈顶do le型数值强制转换成long型数值并将结果压入栈顶 |
0x90 | d2f | 将栈顶do le型数值强制转换成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 | 比较栈顶两do le型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶 |
0x98 | dcmpg | 比较栈顶两do le型数值大小,并将结果(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 | 返回至本地变量 |
0xaa | tableswitch | 用于switch条件跳转,case值连续(可变长度指令) |
0xab | lookupswitch | 用于switch条件跳转,case值不连续(可变长度指令) |
0xac | ireturn | 从当前方法返回int |
0xad | lreturn | 从当前方法返回long |
0xae | freturn | 从当前方法返回float |
0xaf | dreturn | 从当前方法返回do le |
0xb0 | areturn | 从当前方法返回对象引用 |
0xb1 | return | 从当前方法返回void |
0xb2 | getstatic | 获取指定类的静态域,并将其值压入栈顶 |
0xb3 | putstatic | 为指定的类的静态域赋值 |
0xb4 | getfield | 获取指定类的实例域,并将其值压入栈顶 |
0xb5 | putfield | 为指定的类的实例域赋值 |
0xb6 | invokevirtual | 调用实例方法 |
0xb7 | invokespecial | 调用超类构造方法,实例初始化方法,私有方法 |
0xb8 | invokestatic | 调用静态方法 |
0xb9 | invokeinterface | 调用接口方法 |
0xba | – | 无此指令 |
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下一条指令地址压入栈顶 |
1、常量入栈指令
- 该指令的功能是将常数压入操作数栈,根据数据类型和入栈内容的不同,又可以分为const系列,push系列和ldc指令;
- const:用于特定的常量入栈,入栈的常量隐含在指令本身里。
- 比如:aconst_null将null压入操作数栈;iconst_m1将-1压入操作数栈;
- 指令助记符的第一个字符总是喜欢表示数据类型,i表示整数,l表示长整数,f表示浮点数,d表示双精度浮点,习惯上用a表示对象引用。如果指令隐含操作的参数,会以下划线形式给出;
- push:主要包括bipush和sipush,它们的区别在于接收数据类型的不同,bipush接收8位整数作为参数,sipush接收16位整数,它们都将参数压入栈;
- ldc:可接收一个8位的参数,该参数指向常量池中的int,float或者String的索引,将制定的内容压入堆栈;
- 类似的还有ldc_w,它接收两个8位参数,能支持的索引范围大于ldc;
- 如果要压入的元素是long或者double,则使用ldw2_w指令;
2、局部变量压栈指令
- 该指令将给定的局部变量表中的数据压入操作数栈。
- 这类指令大体可以分为:xload(x为i,l,f,d,a),xload_n(x为i,l,f,d,a,n为0到3),xaload(x为i,l,f,d,a,b,c,s);
- x的取值表示数据类型:
- 指令xload_n
- 表示将第n个局部变量压入操作数栈,比如iload_1,fload_0,aload_0等指令。其中aload_n表示将一个对象引用压栈;
- 指令xload
- 通过指定参数的形式,把局部变量压入操作数栈,当使用这个命令时,表示局部变量的数量可能超过了4个,比如指令iload,fload等;
- 指令xaload
- 表示将数组的元素压栈,比如saload,caload分别表示压入short数组和char数组。指令xaload在执行时,要求操作数中栈顶元素为数组索引i,栈顶顺位第2个元素为数组引用a,该指令会弹出栈顶这两个元素,并将a[i]重新压入堆栈;
3、出栈装入局部变量表指令
- 该指令用于将操作数栈中栈顶元素弹出后,装入局部变量表的指定位置,用于给局部变量赋值。这类指令主要以store的形式存在,比如xstore(x为i,l,f,d,a),xstore_n(x为i,l,f,d,a,n为0到3)和xastore(x为i,l,f,d,a,b,c,s)。x的取值含义和load类命令是一样的。
- 指令istore_1
- 从操作数栈中弹出一个整数,并把它赋值给局部变量1。
- 指令xstore
- 没有隐含参数信息,故需要提供一个byte类型的参数类指定目标局部变量表的位置;
- 指令xastore
示例:
- 专门针对数组操作,以iastore为例,它用于给一个int数组的给定索引赋值;在iastore执行前,操作数栈订需要以此准备3个元素:值,索引,数组引用,iastore会弹出这3个值,并将值赋给数组中指定索引的位置;
|
||
其中,第0行字节码,压入常量99,第2行istore将99弹出,并赋给局部变量表6的变量,而该变量正好是X。接着在第4,5,6行分别压入iastore所需的3个参数, |
4、通用型操作
- 该操作提供了无需指明数据类型的操作。比如栈操作,不是在所有时刻对栈的压入或者弹出都必须明确数据类型的。
- 指令NOP
- 字节码为0x00,表示什么也不做。这条指令一般用于调式,占位等;
- 指令dup
- 意为duplicate复制,会将栈顶元素复制一份并再次压入栈顶,这样栈顶就有两份一摸一样的元素了;
- 指令pop
示例:指令dup与pop的说明
- 把一个元素从栈顶弹出,并且直接废弃
|
||
为了生成Object对象,使用了对象创建指令new。创建完成后,new指令会把对象引用放置在栈顶,此时,栈顶只有一份对象引用。但是,在new指令之后,对该obj对象连续进行两次操作:一次是通过invokespecial指令调用对象的构造函数,另一次是通过astore_2将对象赋值给obj。这两个操作都会将栈顶元素弹出,故为了连续两次使用同样的栈顶元素,这里使用指令dup赋值了一份对象引用,供后面连续两次指令使用。在obj.toString()方法执行完毕后,函数的范围值会出现在栈顶,但是由于没有人使用,故简单地使用pop操作将无人问津的返回值直接丢弃; |
||
注意:pop指令只能丢弃一个字长(32位),如果要丢弃栈顶64位数据(long或者double),则需要使用pop2命令,类似地,如果要连续复制栈顶2个字长,则可以使用dup2指令 |
5、类型转换指令
示例:
比如:i2l表示将int数据转为long数据。指令i2l在执行时,先将栈顶的int数据弹出,然后进行转换。最后,将转化后的long型数据压入,如下图,转换后的Long型数字占用两个字空间;
其字节码指令如下: |
查看加粗的字节码,int转换为long使用了i2l,long转换为float使用了l2f, |
示例:byte转换为int和long
|
||
对于byte类型转为int,虚拟机并没有做实质性的转化处理,只是简单地通过操作数栈交换了两个数据。而将byte转为long时,使用的是i2l,可以看到在内部byte在这里等同于int处理,类似的还有short。这种处理方式有两个特点: |
6、运算指令
该指令为虚拟机提供基本的加减乘除运算功能;每种指令也有自己支持的数据类型,使用一个字符表示:
以乘法指令为例,imul表示从操作数栈中弹出两个整数,将它们相乘,结果再压入栈。指令lmul表示long型,fmul表示对float的操作,dmul表示对double的乘法; |
- 示例:数值取反指令改变数字的符号位
生成的字节码如下: |
指令fneg操作前后,操作数栈没有变化,只是栈顶的元素符号位被取反; |
- 示例:指令iinc对给定的局部变量做自增操作,这条指令是少数几个执行过程中完全不修改操作数栈的指令。它接收两个操作数:
第1个局部变量表的位置,第2个位累加数。比如常见的i++,就会产生这条指令
生成的字节码如下: |
在参数中,this,j占据了局部变量表的第0和第1个位置,故i处于第2个位置 |
- 示例:位运算指令由位运算符产生,除了按位取反,其它的位运算符都有对应的指令,现在演示按位取反:
生成的字节码: |
ixor为整数的按位异或操作,它从栈中弹出两个整数,并将它们按位异或,将结果再压入栈中。 注意:-1的2进制表示为一个全1的数字0xFF,任何数字与0xFF异或后,自然取反; |
7、对象/数组操作指令
- 对于对象的操作指令,可进一步细分为创建指令,字段访问指令,类型检查指令,数组操作指令
- 创建指令
- 用于创建对象或数组,主要有:new,newarray,anewarray和multianewarray;
- 该接收一个操作数,为指向常量池的索引,表示要创建的类型,执行完成后,将对象的引用压入栈;
- 示例:指令newarray和anewarray用来创建数组。前者用于创建基本类型的数组,后者用于创建对象数组。指令multianewarray用于创建多维数组。
这段代码创建了一个int数组,一个Object数组和一个int二维数组,因此它依次使用了newarray,anewarray和multianewarray; |
8、字段访问指令
- 该指令专门用于访问类或者对象的字段;主要有:getfield,putfield,getstatic,pustatic4个;
- getfield,putfield用于操作实例对象的字段,getstatic,pustatic用于操作类的静态字段;
- 示例:
|
||
9、类型检查指令
- 该指令有两个:checkcast,instanceof
- checkcast:用于检查类型强制转换是否可以进行。如果可以进行,checkcast指令不会改变操作数栈,否则它会抛出ClassCastException异常;
- instanceof:用来判断给定对象是否是某一个类的实例,它会将判断结果压入操作数栈;
- 示例:
该代码使用了instanceof关键字,并使用了强制转换,它们分别会产生instanceof和checkcast两个字节码 它们都接收一个操作数,并判断栈顶层元素是否可以转为该操作数给定的类型 |
10、比较控制指令
- 该指令代表条件控制。大体上分为比较指令,条件跳转指令,比较条件跳转指令,多条件分支跳转,无条件跳转指令等;
- 比较指令
- 作用:比较栈顶两个元素的大小,并将比较结果入栈。
- 指令:dcmpg,dcmpl,fcmpg,fcmpl,lcmp;首字符d表示double类型,f表示float,l表示long;对于double和float的数字,由于NaN的存在,所有有两个版本,以float为例,有fcmpg和fcmpl两个指令,它们的区别在于数字比较时,若遇到NaN值,处理结果不同;
- 指令fcmpg和fcmpl都从栈中弹出两个操作数,并将它们做比较,设栈顶的元素为v2,栈顶顺位第2位的元素为v1,若v1=v2,则压入0,若v1>v2则压入1,若v1<v2则压入-1。两个指令的不同之处在于,如果遇到NaN值,fcmpg会压入1,而fcmpl压入-1
- 指令dcmpg,dcmpl类似;
- 跳转指令
- 作用:该指令一般与比较指令结合使用。
- 指令:ifeq,iflt,ifle,ifne,ifgt,ifge,ifnull,ifnonnull;这些指令都接收两个字节的操作数,用于计算跳转的位置。
- 统一含义:弹出栈顶元素,测试它是否满足某一个条件,如果满足条件,则跳转到给定位置。在条件跳转指令执行前,一般可以先用比较指令进行栈顶元素的准备,然后再进行条件跳转;
- 示例:
第9行和第10行在栈顶准备了两个比较元素。第11行指令对栈顶两个元素进行比较,第12行ifle获取栈顶的结果,并确认是否需要跳转 |
11、比较条件跳转指令
- 该指令类似于比较指令和条件跳转指令的结合体;
- 指令:if_icmpeq,if_icmpne,if_icmplt,if_icmpgt,if_icmple,if_icmpge,if_acmpeq,if_acmpne;
示例:
- 为助记符加上"if_"后,以字符"i"开头的指令针对int整数操作(包括short和byte),以字符"a"开头的指令表示对象引用的比较;
- 这些指令都接收两个字节的操作数作为参数,用于计算跳转的位置。同时在执行指令时,栈顶需要准备两个元素进行比较。指令执行后,栈顶的这两个元素被清空,且没有任何数据入栈。如果预设条件成立,则执行跳转,否则,继续执行下一条语句;
|
||
第9和第10行将需要比较的数字压入栈,第11行执行比较,如果条件成立则跳转18行输出0,否则继续执行后一条指令输入1 |
- 示例:如果比较的元素是对象,那么就会使用if_acmpeq和if_acmpne指令
|
||
第9,10行和第35,36行,分别压入栈顶元素比较,第21和第37行执行对象引用的比较并确认是否需要跳转 |
12、多条件分支跳转
- 专为switch-case语句设计。主要有tableswitch和lookupswitch;
- 区别:
- tableswitch:要求多个条件分支值是连续的,它内部只存放起始值和终止值,以及若干个跳转偏移量,通过给定的操作数index,可以立即定位到跳转偏移量,因此效率比较高;
- lookupswitch:内部存放着各个离散的case-offset对,每次执行都要搜索全部的case-offset对,找到匹配的case值,并根据对应的offset计算跳转地址,因此效率较低;
由于tableswitch的case值是连续的,因此只需要记录最低值和最高值,以及每一项对应的offset偏移量,给定的index值,计算出对应的offset。 |
lookupswitch处理的是离散的case值,但是处于效率考虑,将case-offset对按照case值大小排序,给定index时,需要查找与index相等的case,获得其offset,如果找不到则跳转到default |
- 示例:tableswitch指令
由于case值是连续的,编译器生成了tableswitch指令来处理switch |
- 示例:lookupswitch指令
case值不连续,使用lookupswitch指令。从字节码体积上看,lookupswitch占用的空间更多。 |
- 示例:JDK1.7中,switch对字符串的处理,使用的是lookupswitch指令
为了支持String的switch操作,在字节码第三行调用了字符串的hashCode()方法,得到int整数。在lookupswitch指令中,实际使用该hash值作为分支的case. 综上,当使用String作为case类型时,虚拟机要多执行hash计算以及字符串相等等操作,性能也会低于直接对int的处理 |
13、无条件跳转
- 该跳转指令为goto。指令jsr,ret虽然也是无条件跳转的,但主要用于try-finally语句,且已经被虚拟机废弃;
- goto接收两个字节的操作数,共同组成一个带符号的整数,用于指定指令的偏移量,指令执行的目的就是跳转到偏离量给定的位置处;
- 如果偏移量太大,超过双字节的带符号整数的范围,则可以使用指令goto_w,它和goto有相同的作用,但它接收4个字节作为操作数,可以达到更宽的地址范围;
14、函数调用与返回指令
- 使虚拟机支持函数调用
- 函数调用指令:invokevirtual,invokeinterface,invokespecial,invokestatic,invokedynamic;
- 函数返回:需要将返回值压入调用者操作数栈,需要使用xreturn指令(x可以是i,l,f,d,a或空)
- 函数指令的作用范围:
- invokevirtual:虚函数调用,调用对象的实例方法,根据对象的实际类型进行派发,支持多态;
- invokeinterface:指接口方法的调用,当被调用对象申明为借口时,使用该指令调用接口的方法;
- invokespecial:调用特殊的一些方法,比如构造函数,类的私有方法,父类的方法。这些方法是静态类型绑定的,不会在调用时进行动态派发;
- invokestatic:调用类的静态方法,这个也是静态绑定的;
- invokedynamic:调用动态绑定的方法,JDK1.7新加入的指令;
- 函数调用结束前,需要进行返回。返回时,使用xreturn指令将返回值存入调用者的操作数栈中。根据返回值,该指令的前缀会不同。
示例:指令invokevirtual的使用
- 返回int时,指令为ireturn,返回为void时,使用return;
- 该指令被调用时,如果方法是同步的,那么调用后,监视器锁将被释放;
以上代码产生如下字节码: |
invokevirtual指令在这里调用了PrintStream实例的println()方法。调用前,操作数栈中将压入调用对象的实例,以及该函数的所有参数。 |
- 示例:invokeinterface指调用接口的函数:
该代码生成了Thread对象,并调用了它的run()方法。Thread类实现了Runnable接口。 |
|
字节码指令中第9行,使用invokevirtual指令,是直接针对Thread对象的调用,第13行,则是针对Runnable接口的调用。和invokervirtual不同,invokeinterface在调用时,需要额外传入1个字节,作为无符号整数,表示这次函数调用所需参数的字数(1字为32位),包含隐含的this。 |
- 示例:invokespecial用于调用特殊的函数,该指令调用时,接收两个字节作为其操作数,用于计算常量池索引入口,且该入口必须为CONSTANT_Methodref。
|
||
指令invokespecial调用了Date类的构造函数 |
- 示例:调用类的私有方法;由于类的私有方法不具有多态性,即使在子类中有相同签名的私有方法,也不能覆盖父类中对应的私有方法的行为,因此对于私有方法调用可以使用静态绑定
invokespecial在调用父类方式,通过操作数直接指向父类的toString()方法,从而避免了子类toString()方法的使用 |
15、同步控制
- Java虚拟机提供了monitorenter,monitorexit来完成临界区的进入和离开。达到多线程的同步;
- 当一个线程进入同步快时,它使用monitroenter指令请求进入,如果当前对象的监视器计数器为0,则它会被准许进入,若为1,则判断持有当前监视器的线程是否为自己,如果是,则进入,否则进行等待,直到对象的监视器计数器为0,才会被允许进入同步块;
- 当线程退出同步块时,需要使用monitorexit申明退出。
- 在java虚拟机中,任何对象都有一个监视器与之相关联,用来判断对象是否被锁定,当监视器被持有后,对象处于锁定状态;
- monitorenter,monitorexit在执行时,都需要在操作数栈顶压入对象,之后,monitorenter,monitorexit的锁定和释放都是针对这个对象的监视器
示例:monitorenter,monitorexit使用示例
- 图示:当线程4离开临界区后,线程1,2,3才有可能进入
在类SyncAdd方法中,有方法add1()和add2(),它们都对当前this对象进行加锁,并对实例字段i进行更新。对于add1(),字节码如下: 此段代码和无同步的代码没有什么区别,没有monitroenter和monitorexit进行同步区控制。 |
|
add2()的字节码如下: |
该段字节码的解析如下: 如果在第4~16行执行期间,遇到任何异常,则进入第19行处理; 第19行将第1个局部变量入栈,该变量就是this,由第2行存入。 |
JVM虚拟机指令的更多相关文章
- Atitit .jvm 虚拟机指令详细解释
Atitit .jvm 虚拟机指令详细解释 1. 一.未归类系列A1 2. 数据mov系列2 2.1. 二.const系列2 2.2. 三.push系列2 2.3. ldc系列 该系列命令负责把数值常 ...
- 翻译:JVM虚拟机规范1.7中的运行时常量池部分(一)
原文链接: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4 Java Virtual Machine i ...
- JVM虚拟机(一) 内存区域
JVM虚拟机内存组成: 如下图: 1. 程序计数器: (1)是一块较小的内存空间:可以看做当前程序执行子界面的行号指示器,字节码解析器执行的时候就是根据这个 ...
- java语言与jvm虚拟机简介
一.java语言 1.1 支持面向对象编程oop 强调支持,因为java同样可以面向过程编程. oop的三大特性是:封装.继承.多态. 封装主要针对成员变量而言,oop的思想要求成员变量均为私有,不应 ...
- 【JVM虚拟机】(8)--深入理解Class中--方法、属性表集合
#[JVM虚拟机](8)--深入理解Class中--方法.属性表集合 之前有关class文件已经写了两篇博客: 1.[JVM虚拟机](5)---深入理解JVM-Class中常量池 2.[JVM虚拟机] ...
- 程序员从宏观、微观角度浅析JVM虚拟机!
1.问题 1.JAVA文本文件如何被翻译成CLASS二进制文件? 2.如何理解CLASS文件的组成结构? 3.虚拟机如何加载使用类文件的生命周期? 4.虚拟机系列诊断工具如何使用? 5.虚拟机内存淘汰 ...
- JVM虚拟机深入理解+GC回收+类加载
旭日Follow_24 的CSDN 博客 ,全文地址请点击: https://blog.csdn.net/xuri24/article/details/81455449 一,前言 本文章是读了“深入理 ...
- 从虚拟机指令执行的角度分析JAVA中多态的实现原理
从虚拟机指令执行的角度分析JAVA中多态的实现原理 前几天突然被一个"家伙"问了几个问题,其中一个是:JAVA中的多态的实现原理是什么? 我一想,这肯定不是从语法的角度来阐释多态吧 ...
- 《深入理解JVM虚拟机》读书笔记
前言:<深入理解JVM虚拟机>是JAVA的经典著作之一,因为内容更偏向底层,所以之前一直没有好好的阅读过.最近因为刚好有空,又有了新目标.所以打算和<构架师的12项修炼>一起看 ...
随机推荐
- invoke方法
主要是为了类反射,这样你可以在不知道具体的类的情况下,根据配置的字符串去调用一个类的方法.在灵活编程的时候非常有用.很多框架代码都是这样去实现的.但是一般的编程,你是不需要这样做的,因为类都是你自己写 ...
- delphi中,write和read的用法?什么时候需要用?
如你所说,在控件或者类的属性中,read 表示 读取,write 则表示设置.比如在类中:TTestClass = (Class)privateFOrderCode:String;publicprop ...
- Lucene之模糊、精确、匹配、范围、多条件查询
Lucene的查询方式很 丰富,对于数值类型的数据,采取TermRangeQuery的方式,对于String类型的,就可以采取TermQuery等,查询方式了,可以通过采取合适的查询方式,检索到数据. ...
- EJB3.0 EJB开发消息驱动bean
(7)EJB3.0 EJB开发消息驱动bean JMS 一: Java消息服务(Java Message Service) 二:jms中的消息 消息传递系统的中心就是消息.一条 Message 由三个 ...
- Docker Warning : the backing xfs filesystem is formatted without d_type support
CentOS7 下安装配置 Docker,遇到如下的WARNING, WARNING: overlay: the backing xfs filesystem is formatted without ...
- winform 开发中 把耗时操作 封装起来 异步执行(.net 4.0)
.先定义一个 BackgroundTask.cs 代码如下: public class BackgroundTask { private static WaitDialogForm LoadingDl ...
- java项目 远程debug
AVA项目无法像PHP那样可以随时修改文件内容进行调试,调试可以借助eclipse,本地代码的话很容易在本地debug,但如果代码已经打包部署在linux上呢?可以进行远程debug 很简单,只需 ...
- B - Bridging signals (LIS)
点击打开链接 B - Bridging signals 'Oh no, they've done it again', cries the chief designer at the Waferlan ...
- Python读取mdb文件以及shell检测
最近写了两个python的脚本不过实际意义不是很大,就是想练练python写程序,一直研究web方面脚本写的少多了,还有C语言也用的少多了.现在有时间得多写写程序,别把以前学到的知识给忘了. 作者: ...
- 2018年Android面试题含答案--适合中高级(上)
这些面试题是我在今年年初换工作的时候整理,没有重点.包括java基础,数据结构,网络,Android相关等等.适合中高级工程师.由于内容过多,将会分为上下两部分.下部分跳转链接:http://www. ...