参考及转载:

(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个值,并将值赋给数组中指定索引的位置;

它生成的字节码中包含istore和iastore指令

其中,第0行字节码,压入常量99,第2行istore将99弹出,并赋给局部变量表6的变量,而该变量正好是X。接着在第4,5,6行分别压入iastore所需的3个参数,
最后调用iastore将77赋值给局部变量表第2位(int[] s)数组的第0个索引位置(s[0])

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、类型转换指令

该指令专门用于类型转换;这类指令的助记符使用x2y的形式给出。其中x可能是i,f,l,d,y可能是i,f,l,d,c,s,b。它们的含义见下表:

示例:

比如:i2l表示将int数据转为long数据。指令i2l在执行时,先将栈顶的int数据弹出,然后进行转换。最后,将转化后的long型数据压入,如下图,转换后的Long型数字占用两个字空间;

其字节码指令如下:

查看加粗的字节码,int转换为long使用了i2l,long转换为float使用了l2f,
long转换为int使用了l2i;

示例:byte转换为int和long

对于byte类型转为int,虚拟机并没有做实质性的转化处理,只是简单地通过操作数栈交换了两个数据。而将byte转为long时,使用的是i2l,可以看到在内部byte在这里等同于int处理,类似的还有short。这种处理方式有两个特点:
1.可以减少实际的数据类型,如果为short和byte都准备一套指令,那么指令的数量会大增,而虚拟机目前的设计上,只愿意使用一个字节表示指令,因此指令总数不能超过256个,为了节省指令资源,将short和byte当作int处理;
2.由于局部变量表中的槽位固定为32位,无论是byte或者short存入局部变量表,都会占用32位空间,从这个角度说,也没有必要特意区分这几种数据类型;

 

6、运算指令

该指令为虚拟机提供基本的加减乘除运算功能;每种指令也有自己支持的数据类型,使用一个字符表示:

以乘法指令为例,imul表示从操作数栈中弹出两个整数,将它们相乘,结果再压入栈。指令lmul表示long型,fmul表示对float的操作,dmul表示对double的乘法;
取余指令用于计算两个数相除后的余数,比如,i%j就会产生irem指令;

  • 示例:数值取反指令改变数字的符号位

生成的字节码如下:

指令fneg操作前后,操作数栈没有变化,只是栈顶的元素符号位被取反;

  • 示例:指令iinc对给定的局部变量做自增操作,这条指令是少数几个执行过程中完全不修改操作数栈的指令。它接收两个操作数:
    第1个局部变量表的位置,第2个位累加数。比如常见的i++,就会产生这条指令

生成的字节码如下:

在参数中,this,j占据了局部变量表的第0和第1个位置,故i处于第2个位置

  • 示例:位运算指令由位运算符产生,除了按位取反,其它的位运算符都有对应的指令,现在演示按位取反:

生成的字节码:

ixor为整数的按位异或操作,它从栈中弹出两个整数,并将它们按位异或,将结果再压入栈中。
在ixor执行前,压入栈中的数字为i=123以及-1(iconst_ml),因此,在虚拟机中,按位取反是通过与-1异或计算得来的。

注意:-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用于操作类的静态字段;
    • 示例:

java编译器会为这条语句产生如下getstatic指令,用于将system.out这个静态字段压入操作数栈。这段指令显示,常量池第21号为Fieldref,它指向System.out静态字段,字段类型为java/io/PrintStream

 

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.
如果hash值没有匹配的,则必然字符串也没有匹配的,因此可以直接执行default出的指令,但如果hash值匹配,考虑到hash冲突的存在,这里并没有进行匹配后的指令,还是对匹配进行二次确认。
在第43行使用String.equals()函数判断字符串是否真的相等。如果确实相等,则执行对应的语句,否则跳转退出。

综上,当使用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()方法。调用前,操作数栈中将压入调用对象的实例,以及该函数的所有参数。
在本例中为System.out实例和字符"aa"。
指令invokevirtual需要两个字节作为操作数,用于计算指向常量池的索引,这里索引必须指向CONSTANT_Methodref入口,表示需要调用的方法;

  • 示例:invokeinterface指调用接口的函数:

该代码生成了Thread对象,并调用了它的run()方法。Thread类实现了Runnable接口。
这里使用两种方式调用run():
第一种直接在Thread申明的实例上调用;
第二种将其转为接口类型Runnable,再进行调用;
这两种调用方式使用的invoke指令是不同的,如左下的图;

字节码指令中第9行,使用invokevirtual指令,是直接针对Thread对象的调用,第13行,则是针对Runnable接口的调用。和invokervirtual不同,invokeinterface在调用时,需要额外传入1个字节,作为无符号整数,表示这次函数调用所需参数的字数(1字为32位),包含隐含的this。
本例,函数没有参数,只需要当前引用this,故数字为1

  • 示例: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进行同步区控制。
因为对于同步方法而言,当虚拟机通过方法的访问标识符判断是一个同步方法时,会自动在方法调用前进行加锁,当同步方法执行完毕后,不管方法是正常结束还是有异常抛出,均会由虚拟机释放这个锁。因此,对于同步方式而言,monitroenter和monitorexit指令是隐式存在的,并未直接出现在字节码中。

add2()的字节码如下:

该段字节码的解析如下:
第0行将this引用入栈;
第1行复制this引用,并入栈;
第2行将this引用弹出,存入第1个局部变量:
第3行根据栈顶的this引用进行加锁;
第4~14行执行了i++操作;
第15行表示释放锁,此时,i++已经完成;
第16行跳转到第22行,并退出;

如果在第4~16行执行期间,遇到任何异常,则进入第19行处理;

第19行将第1个局部变量入栈,该变量就是this,由第2行存入。
第20行根据栈顶的this,退出临界区,释放锁;
第21行抛出当前发生的异常,异常对象位于栈顶;

JVM虚拟机指令的更多相关文章

  1. Atitit .jvm 虚拟机指令详细解释

    Atitit .jvm 虚拟机指令详细解释 1. 一.未归类系列A1 2. 数据mov系列2 2.1. 二.const系列2 2.2. 三.push系列2 2.3. ldc系列 该系列命令负责把数值常 ...

  2. 翻译:JVM虚拟机规范1.7中的运行时常量池部分(一)

    原文链接: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4 Java Virtual Machine i ...

  3. JVM虚拟机(一) 内存区域

    JVM虚拟机内存组成: 如下图:                       1. 程序计数器: (1)是一块较小的内存空间:可以看做当前程序执行子界面的行号指示器,字节码解析器执行的时候就是根据这个 ...

  4. java语言与jvm虚拟机简介

    一.java语言 1.1 支持面向对象编程oop 强调支持,因为java同样可以面向过程编程. oop的三大特性是:封装.继承.多态. 封装主要针对成员变量而言,oop的思想要求成员变量均为私有,不应 ...

  5. 【JVM虚拟机】(8)--深入理解Class中--方法、属性表集合

    #[JVM虚拟机](8)--深入理解Class中--方法.属性表集合 之前有关class文件已经写了两篇博客: 1.[JVM虚拟机](5)---深入理解JVM-Class中常量池 2.[JVM虚拟机] ...

  6. 程序员从宏观、微观角度浅析JVM虚拟机!

    1.问题 1.JAVA文本文件如何被翻译成CLASS二进制文件? 2.如何理解CLASS文件的组成结构? 3.虚拟机如何加载使用类文件的生命周期? 4.虚拟机系列诊断工具如何使用? 5.虚拟机内存淘汰 ...

  7. JVM虚拟机深入理解+GC回收+类加载

    旭日Follow_24 的CSDN 博客 ,全文地址请点击: https://blog.csdn.net/xuri24/article/details/81455449 一,前言 本文章是读了“深入理 ...

  8. 从虚拟机指令执行的角度分析JAVA中多态的实现原理

    从虚拟机指令执行的角度分析JAVA中多态的实现原理 前几天突然被一个"家伙"问了几个问题,其中一个是:JAVA中的多态的实现原理是什么? 我一想,这肯定不是从语法的角度来阐释多态吧 ...

  9. 《深入理解JVM虚拟机》读书笔记

    前言:<深入理解JVM虚拟机>是JAVA的经典著作之一,因为内容更偏向底层,所以之前一直没有好好的阅读过.最近因为刚好有空,又有了新目标.所以打算和<构架师的12项修炼>一起看 ...

随机推荐

  1. invoke方法

    主要是为了类反射,这样你可以在不知道具体的类的情况下,根据配置的字符串去调用一个类的方法.在灵活编程的时候非常有用.很多框架代码都是这样去实现的.但是一般的编程,你是不需要这样做的,因为类都是你自己写 ...

  2. delphi中,write和read的用法?什么时候需要用?

    如你所说,在控件或者类的属性中,read 表示 读取,write 则表示设置.比如在类中:TTestClass = (Class)privateFOrderCode:String;publicprop ...

  3. Lucene之模糊、精确、匹配、范围、多条件查询

    Lucene的查询方式很 丰富,对于数值类型的数据,采取TermRangeQuery的方式,对于String类型的,就可以采取TermQuery等,查询方式了,可以通过采取合适的查询方式,检索到数据. ...

  4. EJB3.0 EJB开发消息驱动bean

    (7)EJB3.0 EJB开发消息驱动bean JMS 一: Java消息服务(Java Message Service) 二:jms中的消息 消息传递系统的中心就是消息.一条 Message 由三个 ...

  5. Docker Warning : the backing xfs filesystem is formatted without d_type support

    CentOS7 下安装配置 Docker,遇到如下的WARNING, WARNING: overlay: the backing xfs filesystem is formatted without ...

  6. winform 开发中 把耗时操作 封装起来 异步执行(.net 4.0)

    .先定义一个 BackgroundTask.cs 代码如下: public class BackgroundTask { private static WaitDialogForm LoadingDl ...

  7. java项目 远程debug

    AVA项目无法像PHP那样可以随时修改文件内容进行调试,调试可以借助eclipse,本地代码的话很容易在本地debug,但如果代码已经打包部署在linux上呢?可以进行远程debug   很简单,只需 ...

  8. B - Bridging signals (LIS)

    点击打开链接 B - Bridging signals 'Oh no, they've done it again', cries the chief designer at the Waferlan ...

  9. Python读取mdb文件以及shell检测

    最近写了两个python的脚本不过实际意义不是很大,就是想练练python写程序,一直研究web方面脚本写的少多了,还有C语言也用的少多了.现在有时间得多写写程序,别把以前学到的知识给忘了. 作者: ...

  10. 2018年Android面试题含答案--适合中高级(上)

    这些面试题是我在今年年初换工作的时候整理,没有重点.包括java基础,数据结构,网络,Android相关等等.适合中高级工程师.由于内容过多,将会分为上下两部分.下部分跳转链接:http://www. ...