JVM指令的速记
在学习的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常量到栈顶
这个类别中有两个指令bipush
和sipush
,比较容易记忆: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类iload
,lload
,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类:
条件分支
条件分支是在编程的时候使用的if
和while
daiifeq
: 当栈顶值等于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关键字的时候,底层的指令是monitorenter
和monitorexit
monitorenter
:获得对象的锁, 用于同步方法或同步块
monitorexit
:释放对象的锁, 用于同步方法或同步块