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

 

字节码   助记符指令含义
0x00nop什么都不做
0x01aconst_null          将null推送至栈顶
0x02iconst_m1将int型-1推送至栈顶
0x03iconst_0将int型0推送至栈顶
0x04iconst_1将int型1推送至栈顶
0x05iconst_2将int型2推送至栈顶
0x06iconst_3将int型3推送至栈顶
0x07iconst_4将int型4推送至栈顶
0x08iconst_5将int型5推送至栈顶
0x09lconst_0将long型0推送至栈顶
0x0alconst_1将long型1推送至栈顶
0x0bfconst_0将float型0推送至栈顶
0x0cfconst_1将float型1推送至栈顶
0x0dfconst_2将float型2推送至栈顶
0x0edconst_0将do le型0推送至栈顶
0x0fdconst_1将do le型1推送至栈顶
0x10bipush将单字节的常量值(-128~127)推送至栈顶
0x11sipush将一个短整型常量值(-32768~32767)推送至栈顶
0x12ldc将int, float或String型常量值从常量池中推送至栈顶
0x13ldc_w将int, float或String型常量值从常量池中推送至栈顶(宽索引)
0x14ldc2_w将long或do le型常量值从常量池中推送至栈顶(宽索引)
0x15iload将指定的int型本地变量
0x16lload将指定的long型本地变量
0x17fload将指定的float型本地变量
0x18dload将指定的do le型本地变量
0x19aload将指定的引用类型本地变量
0x1aiload_0将第一个int型本地变量
0x1biload_1将第二个int型本地变量
0x1ciload_2将第三个int型本地变量
0x1diload_3将第四个int型本地变量
0x1elload_0将第一个long型本地变量
0x1flload_1将第二个long型本地变量
0x20lload_2将第三个long型本地变量
0x21lload_3将第四个long型本地变量
0x22fload_0将第一个float型本地变量
0x23fload_1将第二个float型本地变量
0x24fload_2将第三个float型本地变量
0x25fload_3将第四个float型本地变量
0x26dload_0将第一个do le型本地变量
0x27dload_1将第二个do le型本地变量
0x28dload_2将第三个do le型本地变量
0x29dload_3将第四个do le型本地变量
0x2aaload_0将第一个引用类型本地变量
0x2baload_1将第二个引用类型本地变量
0x2caload_2将第三个引用类型本地变量
0x2daload_3将第四个引用类型本地变量
0x2eiaload将int型数组指定索引的值推送至栈顶
0x2flaload将long型数组指定索引的值推送至栈顶
0x30faload将float型数组指定索引的值推送至栈顶
0x31daload将do le型数组指定索引的值推送至栈顶
0x32aaload将引用型数组指定索引的值推送至栈顶
0x33baload将boolean或byte型数组指定索引的值推送至栈顶
0x34caload将char型数组指定索引的值推送至栈顶
0x35saload将short型数组指定索引的值推送至栈顶
0x36istore将栈顶int型数值存入指定本地变量
0x37lstore将栈顶long型数值存入指定本地变量
0x38fstore将栈顶float型数值存入指定本地变量
0x39dstore将栈顶do le型数值存入指定本地变量
0x3aastore将栈顶引用型数值存入指定本地变量
0x3bistore_0将栈顶int型数值存入第一个本地变量
0x3cistore_1将栈顶int型数值存入第二个本地变量
0x3distore_2将栈顶int型数值存入第三个本地变量
0x3eistore_3将栈顶int型数值存入第四个本地变量
0x3flstore_0将栈顶long型数值存入第一个本地变量
0x40lstore_1将栈顶long型数值存入第二个本地变量
0x41lstore_2将栈顶long型数值存入第三个本地变量
0x42lstore_3将栈顶long型数值存入第四个本地变量
0x43fstore_0将栈顶float型数值存入第一个本地变量
0x44fstore_1将栈顶float型数值存入第二个本地变量
0x45fstore_2将栈顶float型数值存入第三个本地变量
0x46fstore_3将栈顶float型数值存入第四个本地变量
0x47dstore_0将栈顶do le型数值存入第一个本地变量
0x48dstore_1将栈顶do le型数值存入第二个本地变量
0x49dstore_2将栈顶do le型数值存入第三个本地变量
0x4adstore_3将栈顶do le型数值存入第四个本地变量
0x4bastore_0将栈顶引用型数值存入第一个本地变量
0x4castore_1将栈顶引用型数值存入第二个本地变量
0x4dastore_2将栈顶引用型数值存入第三个本地变量
0x4eastore_3将栈顶引用型数值存入第四个本地变量
0x4fiastore将栈顶int型数值存入指定数组的指定索引位置
0x50lastore将栈顶long型数值存入指定数组的指定索引位置
0x51fastore将栈顶float型数值存入指定数组的指定索引位置
0x52dastore将栈顶do le型数值存入指定数组的指定索引位置
0x53aastore将栈顶引用型数值存入指定数组的指定索引位置
0x54bastore将栈顶boolean或byte型数值存入指定数组的指定索引位置
0x55castore将栈顶char型数值存入指定数组的指定索引位置
0x56sastore将栈顶short型数值存入指定数组的指定索引位置
0x57pop将栈顶数值弹出 (数值不能是long或do le类型的)
0x58pop2将栈顶的一个(long或do le类型的)或两个数值弹出(其它)
0x59dup复制栈顶数值并将复制值压入栈顶
0x5adup_x1复制栈顶数值并将两个复制值压入栈顶
0x5bdup_x2复制栈顶数值并将三个(或两个)复制值压入栈顶
0x5cdup2复制栈顶一个(long或do le类型的)或两个(其它)数值并将复制值压入栈顶
0x5ddup2_x1dup_x1 指令的双倍版本
0x5edup2_x2dup_x2 指令的双倍版本
0x5fswap将栈最顶端的两个数值互换(数值不能是long或do le类型的)
0x60iadd将栈顶两int型数值相加并将结果压入栈顶
0x61ladd将栈顶两long型数值相加并将结果压入栈顶
0x62fadd将栈顶两float型数值相加并将结果压入栈顶
0x63dadd将栈顶两do le型数值相加并将结果压入栈顶
0x64is将栈顶两int型数值相减并将结果压入栈顶
0x65ls将栈顶两long型数值相减并将结果压入栈顶
0x66fs将栈顶两float型数值相减并将结果压入栈顶
0x67ds将栈顶两do le型数值相减并将结果压入栈顶
0x68imul将栈顶两int型数值相乘并将结果压入栈顶
0x69lmul将栈顶两long型数值相乘并将结果压入栈顶
0x6afmul将栈顶两float型数值相乘并将结果压入栈顶
0x6bdmul将栈顶两do le型数值相乘并将结果压入栈顶
0x6cidiv将栈顶两int型数值相除并将结果压入栈顶
0x6dldiv将栈顶两long型数值相除并将结果压入栈顶
0x6efdiv将栈顶两float型数值相除并将结果压入栈顶
0x6fddiv将栈顶两do le型数值相除并将结果压入栈顶
0x70irem将栈顶两int型数值作取模运算并将结果压入栈顶
0x71lrem将栈顶两long型数值作取模运算并将结果压入栈顶
0x72frem将栈顶两float型数值作取模运算并将结果压入栈顶
0x73drem将栈顶两do le型数值作取模运算并将结果压入栈顶
0x74ineg将栈顶int型数值取负并将结果压入栈顶
0x75lneg将栈顶long型数值取负并将结果压入栈顶
0x76fneg将栈顶float型数值取负并将结果压入栈顶
0x77dneg将栈顶do le型数值取负并将结果压入栈顶
0x78ishl将int型数值左移位指定位数并将结果压入栈顶
0x79lshl将long型数值左移位指定位数并将结果压入栈顶
0x7aishr将int型数值右(符号)移位指定位数并将结果压入栈顶
0x7blshr将long型数值右(符号)移位指定位数并将结果压入栈顶
0x7ciushr将int型数值右(无符号)移位指定位数并将结果压入栈顶
0x7dlushr将long型数值右(无符号)移位指定位数并将结果压入栈顶
0x7eiand将栈顶两int型数值作“按位与”并将结果压入栈顶
0x7fland将栈顶两long型数值作“按位与”并将结果压入栈顶
0x80ior将栈顶两int型数值作“按位或”并将结果压入栈顶
0x81lor将栈顶两long型数值作“按位或”并将结果压入栈顶
0x82ixor将栈顶两int型数值作“按位异或”并将结果压入栈顶
0x83lxor将栈顶两long型数值作“按位异或”并将结果压入栈顶
0x84iinc将指定int型变量增加指定值(i++, i–, i+=2)
0x85i2l将栈顶int型数值强制转换成long型数值并将结果压入栈顶
0x86i2f将栈顶int型数值强制转换成float型数值并将结果压入栈顶
0x87i2d将栈顶int型数值强制转换成do le型数值并将结果压入栈顶
0x88l2i将栈顶long型数值强制转换成int型数值并将结果压入栈顶
0x89l2f将栈顶long型数值强制转换成float型数值并将结果压入栈顶
0x8al2d将栈顶long型数值强制转换成do le型数值并将结果压入栈顶
0x8bf2i将栈顶float型数值强制转换成int型数值并将结果压入栈顶
0x8cf2l将栈顶float型数值强制转换成long型数值并将结果压入栈顶
0x8df2d将栈顶float型数值强制转换成do le型数值并将结果压入栈顶
0x8ed2i将栈顶do le型数值强制转换成int型数值并将结果压入栈顶
0x8fd2l将栈顶do le型数值强制转换成long型数值并将结果压入栈顶
0x90d2f将栈顶do le型数值强制转换成float型数值并将结果压入栈顶
0x91i2b将栈顶int型数值强制转换成byte型数值并将结果压入栈顶
0x92i2c将栈顶int型数值强制转换成char型数值并将结果压入栈顶
0x93i2s将栈顶int型数值强制转换成short型数值并将结果压入栈顶
0x94lcmp比较栈顶两long型数值大小,并将结果(1,0,-1)压入栈顶
0x95fcmpl比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
0x96fcmpg比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
0x97dcmpl比较栈顶两do le型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
0x98dcmpg比较栈顶两do le型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
0x99ifeq当栈顶int型数值等于0时跳转
0x9aifne当栈顶int型数值不等于0时跳转
0x9biflt当栈顶int型数值小于0时跳转
0x9cifge当栈顶int型数值大于等于0时跳转
0x9difgt当栈顶int型数值大于0时跳转
0x9eifle当栈顶int型数值小于等于0时跳转
0x9fif_icmpeq比较栈顶两int型数值大小,当结果等于0时跳转
0xa0if_icmpne比较栈顶两int型数值大小,当结果不等于0时跳转
0xa1if_icmplt比较栈顶两int型数值大小,当结果小于0时跳转
0xa2if_icmpge比较栈顶两int型数值大小,当结果大于等于0时跳转
0xa3if_icmpgt比较栈顶两int型数值大小,当结果大于0时跳转
0xa4if_icmple比较栈顶两int型数值大小,当结果小于等于0时跳转
0xa5if_acmpeq比较栈顶两引用型数值,当结果相等时跳转
0xa6if_acmpne比较栈顶两引用型数值,当结果不相等时跳转
0xa7goto无条件跳转
0xa8jsr跳转至指定16位offset位置,并将jsr下一条指令地址压入栈顶
0xa9ret返回至本地变量
0xaatableswitch用于switch条件跳转,case值连续(可变长度指令)
0xablookupswitch用于switch条件跳转,case值不连续(可变长度指令)
0xacireturn从当前方法返回int
0xadlreturn从当前方法返回long
0xaefreturn从当前方法返回float
0xafdreturn从当前方法返回do le
0xb0areturn从当前方法返回对象引用
0xb1return从当前方法返回void
0xb2getstatic获取指定类的静态域,并将其值压入栈顶
0xb3putstatic为指定的类的静态域赋值
0xb4getfield获取指定类的实例域,并将其值压入栈顶
0xb5putfield为指定的类的实例域赋值
0xb6invokevirtual调用实例方法
0xb7invokespecial调用超类构造方法,实例初始化方法,私有方法
0xb8invokestatic调用静态方法
0xb9invokeinterface调用接口方法
0xba无此指令
0xbbnew创建一个对象,并将其引用值压入栈顶
0xbcnewarray创建一个指定原始类型(如int, float, char…)的数组,并将其引用值压入栈顶
0xbdanewarray创建一个引用型(如类,接口,数组)的数组,并将其引用值压入栈顶
0xbearraylength获得数组的长度值并压入栈顶
0xbfathrow将栈顶的异常抛出
0xc0checkcast检验类型转换,检验未通过将抛出ClassCastException
0xc1instanceof检验对象是否是指定的类的实例,如果是将1压入栈顶,否则将0压入栈顶
0xc2monitorenter获得对象的锁,用于同步方法或同步块
0xc3monitorexit释放对象的锁,用于同步方法或同步块
0xc4wide<待补充>
0xc5multianewarray创建指定类型和指定维度的多维数组(执行该指令时,操作栈中必须包含各维度的长度值),并将其引用值压入栈顶
0xc6ifnull为null时跳转
0xc7ifnonnull不为null时跳转
0xc8goto_w无条件跳转(宽索引)
0xc9jsr_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行抛出当前发生的异常,异常对象位于栈顶;

 

 

转载于:https://www.cnblogs.com/extjs4/p/8979279.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值