怎敢精——jvm指令

字节码是由操作码opcode和操作数operand组成的。操作码的长度是一个字节-128到127

指令执行的伪代码如下:

do{
  自动计算pc寄存器;
  从PC寄存器的位置取出操作码;
  if(存在操作数) 取出操作数;
  执行操作码定义的操作
}while(处理下一次循环)

指令分类:

加载和存储指令

算术指令

类型转换指令

对象的创建和操作

操作数栈管理指令

控制转移指令

方法调用和返回指令

抛出异常

同步

大多数指令都不直接支持byte,short,char和boolean,处理时都说转化为int类型

 

1、加载和存储指令

格式为 _load_,n表示操作数的个数是非负整数(lconst_要求里面存放long数据),下划线上的内容是操作数类型

如iload_<2>。表示加载第二个变量,iload_<3>表示加载第三个变量  iload  5表示加载第五个变量

怎敢精——jvm指令_第1张图片

简写和对应具体关系如下:

i : int

l : long

f : float

d : double

c : char

b : byte

a : reference(引用)

本地变量加载到操作数栈指令:

iload,iload_,lload,lload_,fload,dload,aload

这里没有byte和char,iload和ilad_0是一样的。其他类似的带n的省略

从操作数栈存储到局部变量表指令:

istore,istore_,lstore,fstore,dstore,astore

类似的省略

常量加载到操作数栈

bipush,sipush,ldc,ldc_w,ldc2_w,aonst_null,iconst_ml  const_  lconst_

上面带n的是一类指令,如iload_表示的是iload_<1>、iload_<2>、等等,但是lconst_指加载long型常量到操作数栈。

加载和存储是一个互逆的过程,加载是从变量到操作数栈,存储是从操作数栈到变量

 

算术指令

算术指令是对两个操作数栈上的值进行某种运算,并将结果重新压入栈

大体分为:对整数计算和对浮点数计算

算术指令
指令名 指令内容
加法指令 iadd  ladd dadd fadd
减法指令 sub   lsub   dsub   fsub

乘法指令

imul   lmul   dmul   fmul
除法指令 idiv   ldiv   ddiv  fdiv
求余指令 irem  lrem   frem   drem
求负值指令 ineg   dneg  lneg   fneg
移位指令 ishl   ishr   ushr   lshl   lshr   lushr
按位或指令 ior    lor
按位与指令 iand    land
按位异或 ixor   lxor
局部变量自增指令 iinc
比较指令 dcmpg    dcmpl    fcmpg   fcmpl   lcmp

虚拟机没有明确规定整形数据溢出的情况。如打印1<<31只会打印负值,但是除以0会抛异常,浮点数除以0不会抛异常,但是会有抛出一个字符串

怎敢精——jvm指令_第2张图片

怎敢精——jvm指令_第3张图片

 

类型转换指令

用于开发人员代码中的显示类型转换或者解决虚拟机不完备的问题(byte,char和boolean的类型转换成int)

Java虚拟机规范SE8中原文提到,类型转换指令其实并不存在,因为byte char和boolean本来就是按int存储的所以不存在转化,觉得这一段与一开始说的类型转化用于虚拟机不完备的情况,有点冲突。

类型转换有两种:宽化与窄化。

宽化是int转long,转double这种小数据向大数据转化,窄化相反。

指令为i2l,i2f,i2d,l2f,l2d,f2d,2表示to 宽化转换通常不会丢失精度

这里因为long占的空间比float要长,不明白为什么是宽化转换,跑了一下,可能是浮点数表示形式允许超过long的范围

怎敢精——jvm指令_第4张图片

 

窄化就是l2i等,通常会丢失精度,丢失部分数值,但是不会抛出异常

 

对象的创建与操作

创建类实例

new

创建数组 newarray   anewarray   multianewayya
访问类字段(静态字段)和类实例字段(非静态字段) getfield   putfield   getstatic   putstatic
将数组加载到操作数栈 baload  caload   saload   iaload  laload   faload   daload   aaload
将一个操作数栈的值存储到数组中 上面的load换成store
取数组长度指令 arraylength
检查类实例或者数组类型指令 instanceof   checkcast

 

 

操作数栈管理指令

pop,pop2,dup,dup2,dup_x1,dup2_x1,dup_x2,dup2_x2  swap

栈顶的一个或两个元素出栈(两个刚好用于计算我觉得) pop  pop2
复制栈顶一个或两个数值并将复制值或双份的复制值重新亚茹栈顶 dup  dup2   dup_x1    dup_x2   dup2_x1   dup2_x2
将栈顶两个元素互换 swap

 

控制转移指令

条件分支 ifeq ifne iflt ifle ifgt ifge ifnull ifnonull  if_icmpeq  if_icmpne  if_icmplt  if_icmpgt  if_icmpge  if_acmpeq if_acmpne
复合条件分支 tableswitch   lookupswitch
无条件分支 goto  goto_w  jsr  jsr_w   ret

boolean byte char short都使用int类型的比较指令来完成,而对于long  flaot double则先执行比较指令,返回一个整数数值到操作数栈中,随后执行int的条件分支完成条件分支

所有的比较最终都会转化为int的比较

 

方法调用和返回指令

invokevirtual指令用于调用对象的实例方法

invokeinterface用于调用接口,在运行时搜索特定对象实现这个接口的方法并调用

invokespecial用于处理一些特殊实例方法,如初始化方法,私有方法和父类方法

invokestatic调用类方法(静态方法)

invokedynamic用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法,前面四条的指令分派固化在java虚拟机内部,而这一条是由用户的引导方法决定的

方法调用指令和数据类型无关,但是返回是数据相关的,如ireturn返回Boolean,char,char,short,int的值,还有lreturn,freturn,dreturn,areturn

还有一条用于返回void,实例化方法和初始化方法使用的return

异常处理指令

由athrow实现,虚拟机规定了一些运行时异常在jvm检测到异常时自动抛出

 

同步指令

jvm可以支持方法级的同步和方法内部的一段指令序列同步,这两种同步结构都是用同步锁(monitor)来实现的。

方法级的同步锁是隐式的,即无需通过字节码指令控制。

可以在方法常量池的方法表结构中的ACC_SYNCHRONNIZED查看一个方法是否同步

当调用方法时,调用指令会先查看是否有同步标记,如果有会让执行线程先持有同步锁,然后执行方法,执行完成后释放锁。

如果一个同步方法遇到异常且方法未对异常做处理,那么会在异常抛出的时候释放锁。

指令序列的同步通常用来表示java的synchronized块,jvm的指令集中用monitorenter和monitorexit来支持这个关键字,一个进入锁一个退出锁。

结构化锁定是指在方法调用期间每一个同步锁退出都与前面同步锁进入的情形相同,因为无法保证所有提交给jvm执行的代码都满足结构化锁定,因此jvm允许通过以下两条规则使结构化锁定成立

假设T是线程,M表示同步锁

1、T在方法执行时持有同步锁M的次数必须与T在方法执行时释放同步锁的次数相等

2、在方法调用过程中,任何时刻都不会出现线程T释放同步锁M的次数比T持有同步锁M次数多的情况

 

对于指令的查看可以使用 javap-c

怎敢精——jvm指令_第5张图片

 public static void main(java.lang.String[]);
    Code:
       0: iconst_1                     // 将常量加载入栈(数值是1-5的时候用iconst)
       1: istore_1                     // 读取栈的数据存储到变量 
       2: bipush        10             // 数值是-128~127的时候用bipush 压入栈 如果arraylength 
                                       // 是5的话这里就是iconst_5
       4: newarray       int           //  创建一个数组
       6: astore_2                     // 将栈中的引用存入到变量中
       7: return                       // 返回

1、前面说的一类 _load_表示多个,但是这个应该是有上限的,如const只能加载1-5

2、超过一定个数就使用类似bipush这种指令了

3、javap 的第二列是表示操作码对应的操作数类型

你可能感兴趣的:(java虚拟机,怎敢精)