java jvm指令记录

入栈指令

iconst_<i> :
i范围:[-1, 5]
将int类型(byte, short,char, boolean)常量i压入操作数栈
i为-1时iconst_m1

lconst_<l>:
l范围:[0, 1]
将long类型常量l压入操作数栈

fconst_<f>:
f范围:[0, 2]
将float类型常量f压入操作数栈

dconst_<d>:
d范围:[0, 1]
将double类型常量d压入操作数栈

dconst_<d>:
d范围:[0, 1]
将double类型常量d压入操作数栈

aconst_null
将引用类型null压入操作数栈


bipush:接收8位整数作为参数,将其压入操作数栈
sipush:接收16位整数作为参数,将其压入操作数栈


ldc:上方指令不能满足的需求用ldc实现。可以收8位的参数, 该参数指向常量池的int,float或者String的索引,将其压入操作数栈
ldc_w接收两个8位参数
压入栈是double或long用ldc2_w


类型常数指令范围
int ( byte, short, char, boolean)iconst[-1, 5]
int ( byte, short, char, boolean)bipush[-128, 127]
int ( byte, short, char, boolean)sipush[-32768, 32767]
int ( byte, short, char, boolean)ldcany int value
longlconst0, 1
longldcany long value
floatfconst0, 1, 2
floatldcany float value
doubledconst0, 1
doubleldcany double value
referenceaconst_nullnull
referenceldc索引
public void pushConstLdc() {
    int i = -1;
    int a = 5;
    int b = 6;
    int c = 127;
    int d = 128;
    int e = 32767;
    int f = 32768;
}
 0: iconst_m1  ----->-1入栈
 1: istore_1     ----->-1存放到局部变量表1号槽
 2: iconst_5     ----->5入栈
 3: istore_2    ----->5存放到局部变量表2号槽
 4: bipush        6    ----->6入栈
 6: istore_3    ----->6存放到局部变量表3号槽
 7: bipush        127    ----->127入栈
 9: istore        4    ----->127存放到局部变量表4号槽
11: sipush        128    ----->128入栈
14: istore        5    ----->128存放到局部变量表5号槽
16: sipush        32767    ----->32767入栈
19: istore        6    ----->32767存放到局部变量表6号槽
21: ldc           #2                  // int 32768    ----->32768入栈
23: istore        7    ----->32768存放到局部变量表7号槽
25: return
public void constLdc() {
    long a1 = 1;
    long a2 = 2;
    float b1 = 2;
    float b2 = 3;
    double c1 = 1;
    double c2 = 2;
    Data d = null;
}
 0: lconst_1  ----->-1入栈
 1: lstore_1    ----->1存放到局部变量表1号槽
 2: ldc2_w        #2                  // long 2l  ----->2入栈
 5: lstore_3    ----->2存放到局部变量表3号槽(long类型占两个槽位)
 6: fconst_2  ----->2入栈
 7: fstore        5    ----->2存放到局部变量表5号槽(long类型占两个槽位)
 9: ldc           #4                  // float 3.0f  ----->3入栈
11: fstore        6    ----->2存放到局部变量表6号槽(float类型占一个槽位)
13: dconst_1  ----->1入栈
14: dstore        7    ----->1存放到局部变量表7号槽(float类型占一个槽位)
16: ldc2_w        #5                  // double 2.0d  ----->2入栈
19: dstore        9    ----->2存放到局部变量表9号槽(double类型占一个槽位)
21: aconst_null  ----->null入栈
22: astore        11    ----->null存放到局部变量表11号槽(double类型占一个槽位)
24: return

比较指令

  • 比较指令的作用是比较栈顶两个元素的大小,并将比较结果入栈
  • 比较指令有:dcmpg、dcmpl、fcmpg、fcmpl、lcmp
    dcmpg,dcmpl为double类型
    fcmpg、fcmpl为float类型
    lcmp为long类型
  • 对于double和float类型的数字,由于存在NaN类型,各有两个版本的比较指令。long型整数无NaN故只需要一套。

说明:
  指令fcmpg和fcmpl都从栈中弹出两个操作数,并将它们作比较,设栈顶元素为v2,栈顶顺位第2位元素为v1,若v1=v2则压入0;若v1>v2则压入1, 若v1<v2则压入-1.不同之处为如果遇到NaN,fcmpg会压入1,fcmpl会压入-1。 dcmpg,dcmpl同理

创建指令

  1. 创建类实例指令: new
      它接收一个操作数,为指向常量池的索引,表示要创建的类型,执行完成后,将对象的引用压入栈
  2. 创建数组指令:newarray、anewarray、multianewarray
    newarray:创建基本类型数组
    anewarray:创建引用类型数组
    multianewarray :创建多维数组
	public void newTest() {
        Object obj = new Object();
        File file = new File("test");
    }

    public void newArray() {
        int[] intArr = new int[10];
        Object[] objArr = new Object[10];
        int[][] intArr2 = new int[10][11];
        String[][] strArr2 = new String[10][];
        String[][] strArr3 = new String[10][11];
    }

newTest

 0: new           #2                  // class java/lang/Object
 3: dup
 4: invokespecial #1                  // Method java/lang/Object."<init>":()V
 7: astore_1
 8: new           #3                  // class java/io/File
11: dup
12: ldc           #4                  // String test
14: invokespecial #5                  // Method java/io/File."<init>":(Ljava/lang/String;)V
17: astore_2
18: return

newArray

 0: bipush        10
 2: newarray       int
 4: astore_1
 5: bipush        10
 7: anewarray     #2                  // class java/lang/Object
10: astore_2
11: bipush        10
13: bipush        11
15: multianewarray #6,  2             // class "[[I"
19: astore_3
20: bipush        10
22: anewarray     #7                  // class "[Ljava/lang/String;"
25: astore        4
27: bipush        10
29: bipush        11
31: multianewarray #8,  2             // class "[[Ljava/lang/String;"
35: astore        5
37: return

数组操作指令

·把一个数组元素加载到操作数栈的指令:baload、caload、saload、iaload、laload、faload、daload、aaload
·将一个操作数栈的值储存到数组元素中的指令:bastore、castore、sastore、iastore、fastore、dastore、aastore

数组类型加载指令存储指令
byte/booleanbaloadbastore
charcaloadcastore
shortsaloadsastore
intialoadiastore
longlaloadlastore
floatfaloadfastore
doubledaloaddastore
referenceaaloadaastore

说明
  指令xaload表示将数组的元素压入栈,比如·saload、caload分别表示压入short数组和char数组。指令xaload在执行时,要求操作数栈顶元素为数组索引i,栈顶顺位第2个元素为数组引用a,该指令会弹出栈顶这两个元素,并将a[i]重新压入栈。
  xastore则专门针对数组操作,以iastore为例,它用于给一个int数组的给定索引赋值。在iastore执行前,操作数栈顶需要以此准备3个元素:值、索引、数组引用,iastore会弹出这3个值,并将值赋值给数组指定索引的位置。

public void arrayTest() {
    int[] arr = new int[10];
    arr[3] = 20;
    System.out.println(arr[1]);
}
 0: bipush        10
 2: newarray       int
 4: astore_1
 5: aload_1
 6: iconst_3
 7: bipush        20
 9: iastore
10: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
13: aload_1
14: iconst_1
15: iaload
16: invokevirtual #7                  // Method java/io/PrintStream.println:(I)V
19: return

取数组长度的指令:arraylength
弹出栈顶数组元素获得其长度并压入栈

public void arrayTest() {
    int[] arr = new int[10];
    System.out.println(arr.length);
}
 0: bipush        10
 2: newarray       int
 4: astore_1
 5: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
 8: aload_1
 9: arraylength
10: invokevirtual #7                  // Method java/io/PrintStream.println:(I)V
13: return

类型检查指令

检查类实例类型的指令:instanceof、checkcast

  • checkcast:用于检查强制转换是否可以进行。如果可以进行,那么checkcast 指令不会改变操作数栈,否则它会抛出ClassCastException异常
  • instanceof:用来判断给顶对象是否是某一个类的实例,它会判断结果是否压入操作数栈
public String fun(Object obj) {
   if (obj instanceof Object) {
        return (String) obj;
    }
    else {
        return null;
    }
}
 0: aload_1 ---->将参数obj入栈
 1: instanceof    #6                  // class java/lang/Object
 4: ifeq          12 ---->如果符合跳到12执行
 7: aload_1
 8: checkcast     #7                  // class java/lang/String
11: areturn ---->引用类型返回
12: aconst_null
13: areturn

方法返回指令

包括ireturn(当返回值是boolean、byte、char、short和int类型时使用)、lreturn、freturn、dreturn和areturn,另外还有一条return指令供声明为void的方法、实例初始化方法、类和接口的类初始化方法使用

说明
通过ireturn指令,将当前操作数栈顶元素弹出,并将这个元素压入调用者的操作数栈中,所有当前操作数栈中的其他元素都会被丢弃。
如果当前返回的是synchronized方法,那么还会执行一个隐藏的monitorexit指令,退出临界区。
最后,会丢弃当前方法的整个帧,恢复调用者的帧,并将控制权转交给调用者。

public void fun() {
  int a = 1;
    int b = doubleInt();
}

public int doubleInt() {
    int i = 10;
    return i;
}
fun():
 0: iconst_1
 1: istore_1
 2: aload_0 ---->this入栈
 3: invokevirtual #6                  // Method doubleInt:()I ---->开启一个新栈帧doubleInt方法,运行完成后栈顶会有doubleInt的返回值
 6: istore_2
 7: return


doubleInt():
 0: bipush        10
 2: istore_1
 3: iload_1
 4: ireturn

操作数栈管理指令

  • 将操作数栈的栈顶一个或两个元素出栈并废弃:pop、pop2
  • 复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2
  • 将栈最顶端的两个slot互换,目前没有交换两个64位数据(long、double)类型的指令:swap
  • nop,是一个非常特殊的指令,它的字节码为0x00。和汇编语言中的nop一样,它表示什么都不做。这条指令一般可用于调试、占位等

说明

  • 不带_x的指令是复制栈顶数据并压入栈顶。包括两个指令,dup和dup2。dup的系数代表要复制的Slot数。
    • dup开头的指令用于复制1个slot的数据。例如1个int或1个reference类型数据。
    • dup2开头的指令用于复制2个slot的数据。例如1个long或2个int类型数据。
  • 带_x的指令是复制栈顶数据并插入栈顶以下的某个位置。共有4个指令,dup_x1, dup2_x1, dup_x2, dup2_x2。对于带_x的复制插入指令,只要将指令的dup和x的系数相加,结果即为需要插入的位置。
    • dup_x1插入位置:1+1=2,即栈顶2个slot下面
    • dup_x2插入位置:1+2=3,即栈顶3个slot下面
    • dup2_x1插入位置:2+1=3,即栈顶3个slot下面
    • dup2_x2插入位置:2+2=4,即栈顶4个slot下面
  • pop:将栈顶的1个slot数值出栈。例如1个short类型数值
  • pop2:将栈顶的2个slot数值出栈。例如1个double类型数值或2个int类型

在这里插入图片描述
在这里插入图片描述

条件跳转指令

指令说明
ifeq当栈顶int型数值等于0时跳转
ifne当栈顶int型数值不等于0时跳转
iflt当栈顶int型数值小于0时跳转
ifge当栈顶int型数值大于等于0时跳转
ifgt当栈顶int型数值大于0时跳转
ifle当栈顶int型数值小于等于0时跳转
ifnull为null时跳转
ifnonnull不为null时跳转
if_icmpeq比较栈顶两int型数值大小,当结果等于0时跳转
if_icmpne比较栈顶两int型数值大小,当结果不等于0时跳转
if_icmplt比较栈顶两int型数值大小,当结果小于0时跳转
if_icmpgt比较栈顶两int型数值大小,当结果大于0时跳转
if_icmple比较栈顶两int型数值大小,当结果小于等于0时跳转
if_icmpge比较栈顶两int型数值大小,当结果大于等于0时跳转
if_acmpeq比较栈顶两引用型数值,当结果相等时跳转
if_acmpne比较栈顶两引用型数值,当结果不相等时跳转

  条件跳转指令通常和比较指令结合使用。在条件跳转指令执行前,一般就可以先用比较指令进行栈顶元素的准备,然后进行条件跳转。
  条件跳转指令1:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull。这些指令都接收两个字节的操作数,用于计算跳转的位置(16位符号整数作为当前位置的offset)
  它们的统一含义为:弹出栈顶元素,测试它是否满足某一条件,如果满足条件,则跳转到给定位置
说明

  • 对于boolean、byte、char、short类型的条件分支比较操作,都是使用int类型的比较指令完成
  • 对于long、float、double类型的条件分支比较操作,则会先执行相应类型的比较运算指令,运算指令会返回一个整数型值到操作数栈中,随后再执行int类型的条件分支比较操作来完成整个分支跳转

  条件跳转指令2:if_icmpeq、if_icmpne、if_icmplt、if_icmpgt、if_icmple、if_icmpge、if_acmpeq和if_acmpne

多条件分支跳转指令

多条件分支跳转指令是专为switch-case语句设计的,主要有tableswitch和lookupswitch。

指令名称描述
tableswitch用于switch条件跳转,case值连续
lookupswitch用于switch条件跳转,case值不连续

· tableswitch要求多个条件分支值是连续的,它内部只存放起始值和终止值,以及若干个跳转偏移量,通过给定的操作数index,可以立即定位到跳转偏移量位置,因此效率高。

  • lookupswitch内部存放着各个离散的case-offset对,每次执行都要搜索全部的case-offset对,找到匹配的case值,并根据对应的offset计算跳转地址,因此效率较低。lookupswitch处理的是离散的case值,但出于效率考虑,将case-offset对按照case值大小进行排序。
public void fun() {
    int num = 10;
    switch (num) {
        case 5:
            num = 1;
            break;
        case 1:
            num = 5;
            break;
        case 10:
            num = -1;
            break;
    }
}
 0 bipush 10
 2 istore_1
 3 iload_1
 4 lookupswitch 3
	1:  45 (+41)
	5:  40 (+36)
	10:  50 (+46)
	default:  52 (+48)
40 iconst_1
41 istore_1
42 goto 52 (+10)
45 iconst_5
46 istore_1
47 goto 52 (+5)
50 iconst_m1
51 istore_1
52 return

jdk新特性
public void fun() {
   String str = "aaa";
    switch (str) {
        case "aaa":
            break;
        case "bbb":
            break;
        case "ccc":
            break;
    }
}
  0 ldc #2 <aaa>
  2 astore_1
  3 aload_1
  4 astore_2
  5 iconst_m1
  6 istore_3
  7 aload_2
  8 invokevirtual #3 <java/lang/String.hashCode>
 11 lookupswitch 3  ---->先根据hasecode值进行排序
	96321:  44 (+33)
	97314:  58 (+47)
	98307:  72 (+61)
	default:  83 (+72)
 44 aload_2
 45 ldc #2 <aaa>
 47 invokevirtual #4 <java/lang/String.equals> ---->再用equals判断是否相等
 50 ifeq 83 (+33)
 53 iconst_0
 54 istore_3
 55 goto 83 (+28)
 58 aload_2
 59 ldc #5 <bbb>
 61 invokevirtual #4 <java/lang/String.equals>
 64 ifeq 83 (+19)
 67 iconst_1
 68 istore_3
 69 goto 83 (+14)
 72 aload_2
 73 ldc #6 <ccc>
 75 invokevirtual #4 <java/lang/String.equals>
 78 ifeq 83 (+5)
 81 iconst_2
 82 istore_3
 83 iload_3
 84 tableswitch 0 to 2	0:  112 (+28)
	1:  115 (+31)
	2:  118 (+34)
	default:  118 (+34)
112 goto 118 (+6)
115 goto 118 (+3)
118 return

无条件跳转

目前主要的无条件跳转指令为goto。指令goto接收两个字节的操作数,共同组成一个带符号的整数,用于指定指令的偏移量,指令执行的目的就是跳转到偏移量给定的位置处。
如果指令偏移量太大,超过双字节的带符号整数范围,则可以使用goto_w,它和goto有相同的作用,但是它接收4个字节的操作数,可以表示更大的地址范围。
指令jsr、jsr_w、ret虽然也是无条件跳转,但主要用于try-finally语句,且已经被虚拟机逐渐废弃

指令名称描述
goto无条件跳转
goto_w无条件跳转(宽索引)
jsr跳转到指定16位offset位置,并将jsr下一条指令地址压入栈顶
jsr_w跳转到指定32位offset位置,并将jsr_w下一条指令地址压入栈顶
ret返回至由指定的局部变量所给出的指令位置(一般与jsr、jsr_w联合使用)

抛出异常指令

  在Java程序中显式抛出异常的操作(throw语句)都由athrow指令来实现,除了用throw语句显式抛 出异常的情况之外,《Java虚拟机规范》还规定了许多运行时异常会在其他Java虚拟机指令检测到异常 状况时自动抛出。例如前面介绍整数运算中,当除数为零时,虚拟机会在idiv或ldiv指令中抛出 ArithmeticException异常。 而在Java虚拟机中,处理异常(catch语句)不是由字节码指令来实现的(很久之前曾经使用jsr和 ret指令来实现,现在已经不用了),而是采用异常表来完成。

  正常情况下,操作数栈的压入都是一条条指令完成的。唯一的例外情况是在抛异常时,java虚拟机会清除操作数栈上的所有内容,而后将异常压入操作数栈上。

异常及异常的处理:

过程一:异常对象的生成---->throw ---->指令:athrow

过程二:异常的处理:抓抛模型---->throw---->使用异常表

public void fun(int i) {
    if (i == 0) {
        throw new RuntimeException("i==0");
    }
}

  0: iload_1
  1: ifne          14
  4: new           #6                  // class java/lang/RuntimeException
  7: dup
  8: ldc           #7                  // String i==0
 10: invokespecial #8                  // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
 13: athrow
 14: return

同步指令

  java虚拟机支持两种同步结构:方法级的同步和方法内部一段指令序列的同步,这两种同步都是使用monitor(更常见的是直接将它称为“锁”)来支持的。

  方法级的同步是隐式的,无须通过字节码指令来控制,它实现在方法调用和返回操作之中。虚拟 机可以从方法常量池中的方法表结构中的ACC_SYNCHRONIZED访问标志得知一个方法是否被声明为 同步方法。当方法调用时,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如 果设置了,执行线程就要求先成功持有管程,然后才能执行方法,最后当方法完成(无论是正常完成 还是非正常完成)时释放管程。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获取 到同一个管程。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同 步方法所持有的管程将在异常抛到同步方法边界之外时自动释放。

  同步一段指令集序列通常是由Java语言中的synchronized语句块来表示的,Java虚拟机的指令集中 有monitorenter和monitorexit两条指令来支持synchronized关键字的语义,正确实现synchronized关键字 需要Javac编译器与Java虚拟机两者共同协作支持。

 当一段代码进入同步代码块时,它使用monitorenter指令请求进入。如果当前对象的监视器计数器为0,则它会被准许进入。若为1.则判断持有当前监视器的线程是否为自己,如果是,则进入,否则进行等待,直到对象的监视器计数器为0,才会被允许进入同步块。

  当前线程退出同步块时,需要使用monitorexit声明退出。在java虚拟机中,任何对象都有一个监视器与之相关联,用来判断对象是否被锁定,当监视器被持有后,对象处于锁定状态。

void onlyMe(Foo f) {
    synchronized(f) {
    	doSomething();
    }
}
Method void onlyMe(Foo)
0 aload_1 // 将对象f入栈
1 dup // 复制栈顶元素(即f的引用)
2 astore_2 // 将栈顶元素存储到局部变量表变量槽 2中
3 monitorenter // 以栈定元素(即f)作为锁,开始同步
4 aload_0 // 将局部变量槽 0(即this指针)的元素入栈
5 invokevirtual #5 // 调用doSomething()方法
8 aload_2 // 将局部变量Slow 2的元素(即f)入栈
9 monitorexit // 退出同步
10 goto 18 // 方法正常结束,跳转到18返回
13 astore_3 // 从这步开始是异常路径,见下面异常表的Taget 13
14 aload_2 // 将局部变量Slow 2的元素(即f)入栈
15 monitorexit // 退出同步
16 aload_3 // 将局部变量Slow 3的元素(即异常对象)入栈
17 athrow // 把异常对象重新抛出给onlyMe()方法的调用者
18 return // 方法正常返回
Exception table:
FromTo Target Type
4 10 13 any
13 16 13 any

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值