JVM指令的速记

付威     2020-05-03   4100   11min  

在学习的JVM的时候,最重要的是认识JVM的指令,JVM指令很多,为了方便记忆,可以根据前缀和功能进行分类:

例如:nop指令代表是一个空指令,JVM收到指令后,什么都不用做,等待下一个指令。


const把数据推至栈顶

const的范围从0x01–0x0f,负责把数据推送到栈顶。例如:iconst_0负责吧整型的0推送到栈顶。 fconst_0负责把float的0推送到栈顶。const可以分为以下几种类型:

iconst_:把int推送栈顶
fconst_:推送float类型栈顶
lconst_:推送long到栈顶
dconst_:提送double至栈顶

上述的为简写的前缀,后续可以跟参数,例如:iconst_0,iconst_1分别代表推送0和1至栈顶。


push常量到栈顶

这个类别中有两个指令bipushsipush,比较容易记忆:bipush推送的是-128~127之间的数据,sipush推送一个端整型的常量。


从常量池中推送至栈顶

这个类别有三个指令,ldc,ldc_w,ldc2_w三个指令。

ldc:将 int,float 或 String 型常量值从常量池中推送至栈顶
ldc_w:将 int,float 或 String 型常量值从常量池中推送至栈顶(宽索引)
ldc2_w: 将 long 或 double 型常量值从常量池中推送至栈顶(宽索引)

其中,ldc_w和ldc2_w属于宽索引指令,即指令对应的(索引值)参数为2个字节。而ldc指令对应的(索引值)参数为1个字节。


load加载指令

load指令是加载把指定的本地变量推送的至栈顶,范围是0x15~0x3a。 例如iload_0是将第一个int型的变量推送至栈顶。load指令也分为4类iloadlload,fload,dload,aload.


aload数组加载指令

aload可以理解成load的加载的数组类型,类型也基本相同,不再赘述dstore。


store 将栈顶的数据存入本地变量

store是将栈顶的栈顶的数据存入本地变量,范围0x36~0x4e。按类型的区分:

istore:将栈顶 int 型数值存入指定本地变量,
lstore:将栈顶 long 型数值存入指定本地变量,
fstore:将栈顶 float 型数值存入指定本地变量,
dstore:将栈顶 double 型数值存入指定本地变量,
astore:将栈顶引用类型数值存入指定本地变量

同样store也存在对应的数组类型astore具体逻辑类似。


栈的操作

pop:栈顶的元素弹出
pop2: 将栈顶的一个(long 或 double 类型)或两个数值(非 long 或 double 的其他类型)弹出
dup:复制栈顶的数值并压入栈

dup指令存在几个衍生的版本:

dup_x1: 复制栈顶的数值,并将两个复制的值压入栈
dup_x2:复制栈顶数值并将三个(或两个)复制值压入栈顶
dup2:复制栈顶一个(long 或 double 类型)或两个(非 long 或 double 的其他类型)数值并将复制值压入栈顶
dup2_x1:dup_x1 指令的双倍版本
dup2_x2:dup_x2 指令的双倍版本

swap:交换两个栈顶的数据,数值不能是long或者double类型


运算指令

JVM对于加减乘除和位运算都提供了不同的指令:

add:将栈顶相加并将结果压入栈顶
sub:将栈顶数值相减并将结果压入栈顶
mul:将栈顶数值相乘并将结果压入栈顶
div:将栈顶数值相除并将结果压入栈顶
rem:将栈顶数值作取模运算并将结果压入栈顶
neg:将栈顶数值取负并将结果压入栈顶
shl:将数值左移指定位数并将结果压入栈顶
shr:将数值右移(带符号)指定位数并将结果压入栈顶
ushr:将数值右移(无符号)指定位数并将结果压入栈顶
and:将栈顶两数值”按位与”并将结果压入栈顶
or:将栈顶两数值”按位或”并将结果压入栈顶
xor:将栈顶两数值”按位异或”并将结果压入栈顶

iinc:将指定 int 型变量增加指定值,常用于i++,i+=2等

运算指令同样存在类型的区别,比如iadd用于int的相加,ladd用于long的相加。


类型转换

在java中存在类型的转换,有的是隐形的,有的是显性的。

(1) 对于宽化类型转换(小范围向大范围转换),无需显式的转换指令,并且是安全的操作。各种范围从小到大依次排序: int, long, float, double。

(2)对于窄化类型转换,必须显式地调用类型转换指令,并且该过程很可能导致精度丢失。转换规则中需要特别注意的是当浮点值为NaN, 则转换结果为int或long的0。虽然窄化运算可能会发生上/下限溢出和精度丢失等情况,但虚拟机规范明确规定窄化转换U不可能导致虚拟机抛出异常。

对于转换指令比较容易记忆,在两个类型中间使用2进行分割,比如i2l是int转long ,i2f代表int 转float等,更多的请参考官方文档。


条件控制


比较指令

cmp是比较栈顶的两个元素大小的值,指令有:
cmpl:比较栈顶两数值大小, 并将结果(1, 0 或-1)压入栈顶;当其中一个数值为 NaN 时, 将-1 压入栈顶, cmpg:比较栈顶两数值大小, 并将结果(1, 0 或-1)压入栈顶;当其中一个数值为 NaN 时, 将1 压入栈顶

同样也存在类型的区别,fcmpl,dcmpl分别代表不同的浮点型和双精度类型。


控制指令

JVM的控制指令是指有条件或无条件地修改PC寄存器的值,从而达到控制流程的目标 ,共分成3类:

  • 条件分支
    条件分支是在编程的时候使用的ifwhiledai
    • ifeq: 当栈顶值等于0的时候跳转
    • ifne:当栈顶值不等于0的时候跳转
    • iflt:当栈顶值小于0的时候跳转
    • ifge:当栈顶值大于等于0的时候跳转
    • ifgt:当栈顶值大于0的时候跳转
    • ifle:当栈顶值小于等于0的时候跳转

    对于两个值的比较可以使用if_icmpeq(其中,if_是前缀,i代表int,eq代表相等),同样可以得到下面的指令:

    • if_icmpcq: 如果两个int值相等,则跳转
    • if_icmpne: 如果两个int类型值不相等,则跳转
    • if_icmplt: 如果一个int类型值小于另外一个int类型值,则跳转
    • if_icmpge: 如果一个int类型值大于或者等于另外一个int类型值,则跳转
    • if_icmpgt: 如果一个int类型值大于另外一个int类型值,则跳转
    • if_icmple: 如果一个int类型值小于或者等于另外一个int类型值,则跳转

    是否为空的时候跳转:

    • ifnull 为 null 时跳转
    • ifnonnull 不为 null 时跳转
  • 复合条件

    复合条件是switch关键字,jvm指令对应的的两个指令:
    tableswitch:用于 switch 条件跳转, case 值连续(可变长度指令)
    lookupswitch:用于 switch 条件跳转, case 值不连续(可变长度指令)

  • 无条件

    无条件的跳转在实际的编程中很少使用,goto语句,在while中实际也是goto指令的作用,无条件跳转有下面三个指令:

    goto:无条件跳转

    goto_w: 无条件跳转(宽索引)

    jsr:跳转至指定的 16 位 offset 位置, 并将 jsr 的下一条指令地址压入栈顶

    jsr_w:同jsr,此指令为宽索引

    ret:返回至本地变量指定的 index 的指令位置(一般与 jsr 或 jsr_w 联合使用)


return 方法的返回

return指令是方法的end的指令,存在的不同类型的返回值,例如ireturn返回int类型,lreturn 返回long型,return代表void的类型返回。


静态几个指令

getstatic: 获取指定类的静态域, 并将其压入栈顶

putstatic: 为指定类的静态域赋值

getfield: 获取指定类的实例域, 并将其压入栈顶

putfield: 为指定类的实例域赋值


方法调用

方法调用类型主要有5种类型:

invokevirtual: 调用实例方法

invokespecial :调用超类构建方法, 实例初始化方法, 私有方法

invokestatic: 调用静态方法

invokeinterface:调用接口方法

invokedynamic:调用动态方法


数组指令

newarray:创建一个指定的原始类型(如 int, float, char 等)的数组,并将其引用值压入栈顶

anewarray: 创建一个引用型(如类, 接口, 数组)的数组, 并将其引用值压入栈顶

arraylength: 获取数组的长度值并压入栈顶

multianewarray: 创建指定类型和指定维度的多维数组, 并将其引用压入栈顶


对象指令

new: 创建一个实例对象。

checkcast:检验类型转换, 检验未通过将抛出 ClassCastException

instanceof:检验对象是否是指定类的实际, 如果是将 1 压入栈顶, 否则将 0 压入栈顶


异常

athrow:将栈顶的异常抛出

并发指令

在synchronized关键字的时候,底层的指令是monitorentermonitorexit

monitorenter:获得对象的锁, 用于同步方法或同步块

monitorexit:释放对象的锁, 用于同步方法或同步块

(本文完)

作者:付威

博客地址:http://blog.laofu.online

如果觉得对您有帮助,可以下方的RSS订阅,谢谢合作

如有任何知识产权、版权问题或理论错误,还请指正。

本文是付威的网络博客原创,自由转载-非商用-非衍生-保持署名,请遵循:创意共享3.0许可证

交流请加群113249828: 点击加群   或发我邮件 laofu_online@163.com

付威

获得最新的博主文章,请关注上方公众号