JVM详解_指令集

2021-11-21  本文已影响0人  wo883721

一. 前置内容

本篇文章是对JVM 指令集的详解,为了防止读者没有接触过这方面内容,对读懂指令集的前置知识做一个简单介绍。

1.1 数据类型

众所周知,java 的数据类型分为两种,原始类型和引用类型,但是在 JVM 中还是有一点小区别的。

1.1.1 原始类型

JVM 中原始类型包括三种 数值类型(numeric types), boolean 类型和 returnAddress 类型。

1.1.1.1 数值类型(numeric types)

. byte 表示8 位有符号二进制整数,默认值是 0
. short 表示16 位有符号二进制整数,默认值是 0
. int 表示32 位有符号二进制整数,默认值是 0
. long 表示64 位有符号二进制整数,默认值是 0
. char 表示16 位无符号二进制整数,默认值是 '\u0000'
. float 表示32 位单精度浮点数,默认值是 0
. double 表示64 位双精度浮点数,默认值是 0

1.1.1.2 boolean 类型

JVMboolean 类型其实就是用 int 类型代替,int 类型的 0 表示 falseint 类型的 1 表示 true

可能有读者疑惑了,为啥不用 byte 类型代替呢,这样更节省空间;其实接下来你就能明白,在 JVM 的虚拟机栈中,byte,short,charboolean 全部都当成 int 类型操作的。

1.1.1.3 returnAddress 类型

这个在 java 语言中没有对应的类型,与 jsr, retjsr_w 这三个指令有关,原来是用于处理异常跳转和 finally 语句的,但是 JVM7 之后,已经不使用这三个指令处理异常跳转和 finally 语句,也就是说你也看不到这个类型了。

1.1.2 引用类型

JVM 中引用类型分为三种 类类型(class types),数组类型(array types)和接口类型(interface types)。

引用类型有一个特殊值 null, 当引用类型不指向任何对象时,它的值就是 null;引用类型默认值就是 null

1.2 运行时数据区

JVM运行时数据区.png
  • 虚拟机栈(Java Virtual Machine Stacks), 本地方法栈(Native Method Stacks) 和程序计数器(Program Counter Register) 都是每个线程独有的数据区。
  • 堆(Heap) 和 方法区(Method Area) 是所有线程共享的数据区。
  1. 程序计数器(Program Counter Register)
    • 指向当前线程虚拟机栈中正在执行方法的字节码指令地址。也就是记录当前线程运行到那里了。
    • 但是如果当前运行的方法是本地方法栈中的,即native 方法,那么这个值就是 undefined
  2. 虚拟机栈(Java Virtual Machine Stacks)
    • 就是一个栈的数据机构,里面储存的数据叫做栈帧(Frame)。
    • 每一个栈帧其实表示一个 java 方法(native 方法除外),也就是说方法调用就是栈帧在虚拟机栈入栈和出栈的过程。
  3. 本地方法栈(Native Method Stacks)
    • 用来储存 native 方法调用信息的。
  4. 堆(Heap)
    • 所有引用类型的数据都是存放在这里,被所有线程共享。
    • GC 来实现内存自动管理。
  5. 方法区(Method Area)
    • 储存了每个类的结构信息。
    • 包括运行期常量池(Run-Time Constant Pool),字段(field)和方法(method)数据。

1.3 虚拟机栈的栈帧

栈帧是本篇文章最重要的部分,不了解它,你就不知道指令集的操作流程。
栈帧用于存储数据和部分过程结果,以及执行动态链接、方法返回值和异常分派。

  • 栈帧随着方法调用而创建,随着方法结束而被摧毁(无论方法是正常调用完成,还是异常调用完成)。
  • 每个栈帧都有自己的局部变量表(local variables),操作数栈(operand stack) 和当前栈帧代表的方法所属类运行时常量池的引用。
  • 栈帧的局部变量表和操作数栈的大小在编译期就已经确定,放在方法的 Code 属性中。也就是说运行期创建栈帧时,是知道栈帧的大小的。

在当前线程虚拟机栈中,只有正在执行方法的那个栈帧是活跃的。这个栈帧被称为当前栈帧(current frame),这个栈帧对应的方法称为当前方法(current frame),定义这个方法的类称为当前类(current method)。

对局部变量表和操作数栈的各种操作,通常都指的是对前栈帧的操作。

1.3.1 局部变量表(local variables)

每个栈帧都包括一个储存局部变量的数组,称为局部变量表。

一共有多少个局部变量,在编译期时就可以知道,将这个值存在方法的 Code 属性max_stack 变量中。

虽然我们在编译期有多少个局部变量,但是我们也要知道每个局部变量的大小,才能计算局部变量表需要多大空间啊。
在前面介绍的JVM 数据类型时,它们的大小都不一样,该怎么办?
其实是这样的,JVM 虚拟机规范中:

  1. 一个局部变量的容量可以储存 boolean, byte, char, short, int, float, referencereturnAddress 这八种数据类型。
    • 一个局部变量的容量也称为一个变量槽,槽是局部变量表的最小单位,上面这八种数据类型都可以使用一个槽来存储。
    • 在这里你就也明白了,在栈帧中 boolean, byte, char, short这四个类型也是用一个槽存储,相当于使用一个 int 类型操作。
  2. 局部变量表可以通过索引来定位访问

    索引从 0 开始,一个局部变量就占据一个索引。

  3. 两个局部变量的容量存储longdouble 类型。
    • 这两个类型比较特殊,必须使用连续的两个局部变量(槽)来存储;也就是说占据两个索引,使用较小的索引值来定位。
    • 例如一个 long 的类型数据储存在索引值为 n 的局部变量中,实际上 nn + 1 一起用来储存 long的值;不能单独读取 n + 1 索引的值;可以在 n + 1 索引处写入值,但是写入之后,索引 n 表示的 long 数据就失效了。

局部变量表中储存那些数据呢?

1.3.2 操作数栈(operand stack)

操作数栈是一个栈的数据结构,配合JVM 指令来进行程序运算的,因为 JVM 就是一个基于栈的虚拟机。

  • JVM 的指令是先将数据复制到操作数栈中,然后根据不同的字节码,进行运算的。
  • 例如 iadd 字节码指令,将两个 int 类型数据进行相加。就是首先将两个 int 类型数据放入操作数栈顶,然后执行iadd 字节码指令,将两个 int 类型数据出栈,进行相加,再将结果值入栈。

运行时栈帧操作数栈最大深度其实也在编译期就可以知道了,也是存放在方法 Code 属性的max_locals 中。

最大深度其实就是这个方法运行期间,操作数栈最多的局部变量数。

1.3.3 动态链接

指向栈帧代表的方法所属类运行时常量池的引用,这样在方法中调用其他方法或者访问成员变量时,就可以通过常量池中对应引用找到真实的引用。

二. 指令

JVM 中,一条指令是由一个要完成操作的操作码(字节码) 和零个或者多个的操作数来组成的。

  • JVM 使用一个字节表示操作码,也就是说最多拥有 256 个操作码。
  • 操作需要的数据,由操作码后面的操作数和操作数栈中数据组成,具体看各个操作码指令的定义。

JVM 中的指令分为 Constants,Loads,Stores,Stack,Math,Conversions,Comparisons,References,Control,ExtendedReserved11 个种类,下面我们将一一介绍。

Reserved 属于JVM保留指令,分别是 (0xca) breakpoint,(0xfe) impdep1(0xff) impdep2

三. Constants 类型指令

Constants 类型指令都是将常量数据存入操作数栈中。

操作码 助记符 作用
0 (0x0) nop 什么也不做
1 (0x1) aconst_null 将常量 null 存入操作数栈
2 (0x2) iconst_m1 int类型常量 -1 放入操作数栈中
3 (0x3) iconst_0 int类型常量 0 放入操作数栈中
4 (0x4) iconst_1 int类型常量 1 放入操作数栈中
5 (0x5) iconst_2 int类型常量 2 放入操作数栈中
6 (0x6) iconst_3 int类型常量 3 放入操作数栈中
7 (0x7) iconst_4 int类型常量 4 放入操作数栈中
8 (0x8) iconst_5 int类型常量 5 放入操作数栈中
9 (0x9) lconst_0 long类型常量 0 放入操作数栈中
10 (0xa) lconst_1 long类型常量 1 放入操作数栈中
11 (0xb) fconst_0 float类型常量 0.0 放入操作数栈中
12 (0xc) fconst_1 float类型常量 1.0 放入操作数栈中
13 (0xd) fconst_2 float类型常量 2.0 放入操作数栈中
14 (0xe) dconst_0 double类型常量 0.0 放入操作数栈中
15 (0xf) dconst_1 double类型常量 1.0 放入操作数栈中
16 (0x10) bipush 将一个字节有符号int类型常量放入操作数栈中
17 (0x11) sipush 将两个字节有符号int类型常量放入操作数栈中
18 (0x12) ldc 通过一个字节无符号索引将常量池中的 int,float,reference类型常量放入操作数栈中
19 (0x13) ldc_w 通过两个字节无符号索引将常量池中的 int,float,reference类型常量放入操作数栈中
20 (0x14) ldc2_w 通过两个字节无符号索引将常量池中的 longdouble 类型常量放入操作数栈中

3.1 nop

3.2 aconst_null

3.3 iconst_<i>

3.4 lconst_<l>

3.5 fconst_<f>

3.6 dconst_<d>

3.7 bipush

3.7 sipush

3.8 ldc

3.9 ldc_w

3.10 ldc2_w

3.11 小结

针对五种类型的常量操作

  1. 引用类型 reference
  • 通过 aconst_null 指令将null 入栈。
  • 通过 ldcldc_w 从常量池中获取reference常量入栈。
  1. int 类型
  • 通过 iconst_<i>,bipushsipush-32768 --> 32767 范围内的整数入栈。也就是说这个范围内的整数是不用放入常量池的。
  • 通过 ldcldc_w 从常量池中获取int类型常量入栈。
  1. float 类型
  • 通过 fconst_0,fconst_1,fconst_20.0 , 1.02.0 这三个float类型数据入栈。也就是说除了这三个float类型数据外,其他的float类型数据都会放在常量池中。
  • 通过 ldcldc_w 从常量池中获取float类型常量入栈。
  1. long 类型
  • 通过 lconst_0,lconst_101这两个long类型数据入栈。也就是说除了这两个long类型数据外,其他的long类型数据都会放在常量池中。
  • 通过 ldc2_w 从常量池中获取long类型常量入栈。
  1. double 类型
  • 通过 dconst_0,dconst_10.01.0这两个double类型数据入栈。也就是说除了这两个double类型数据外,其他的double类型数据都会放在常量池中。
  • 通过 ldc2_w 从常量池中获取double类型常量入栈。

3.12 实例

 public void test() {
        Object ob = null;
        int i = -1;
        i = 1;
        i = 2;
        i = 3;
        i = 4;
        i = 5;
        i = 127;
        i = 129;

        long l = 0;
        l = 1;
        l = 2;

        float f = 0;
        f = 1;
        f = 2;
        f = 3;

        double d = 0;
        d = 1;
        d = 2;
    }

对应的方法字节码

 0 aconst_null
 1 astore_1
 2 iconst_m1
 3 istore_2
 4 iconst_1
 5 istore_2
 6 iconst_2
 7 istore_2
 8 iconst_3
 9 istore_2
10 iconst_4
11 istore_2
12 iconst_5
13 istore_2
14 bipush 127
16 istore_2
17 sipush 129
20 istore_2
21 lconst_0
22 lstore_3
23 lconst_1
24 lstore_3
25 ldc2_w #6 <2>
28 lstore_3
29 fconst_0
30 fstore 5
32 fconst_1
33 fstore 5
35 fconst_2
36 fstore 5
38 ldc #8 <3.0>
40 fstore 5
42 dconst_0
43 dstore 6
45 dconst_1
46 dstore 6
48 ldc2_w #9 <2.0>
51 dstore 6
53 return

四. Loads 类型指令

Loads 类型指令包括从局部变量表(local variables) 中加载数据到操作数栈中,以及从数组中加载数据到操作数栈。

从局部变量表中加载数据:

  • 通过索引从局部变量表加载数据到操作数栈中。
  • 操作数栈栈顶会多一个数据。

从数组中加载数据:

  • 通过数组引用arrayref 获取数组下标index 数据,放入操作数栈中。
  • 操作数栈栈顶会多一个数据。
操作码 助记符 作用
21 (0x15) iload 通过索引从局部变量表加载int 类型数据到操作数栈中
22 (0x16) lload 通过索引从局部变量表加载long 类型数据到操作数栈中
23 (0x17) fload 通过索引从局部变量表加载float 类型数据到操作数栈中
24 (0x18) dload 通过索引从局部变量表加载double 类型数据到操作数栈中
25 (0x19) aload 通过索引从局部变量表加载double 类型数据到操作数栈中
26 (0x1a) -> 29 (0x1d) iload_<n> 通过索引<n>从局部变量表加载int 类型数据到操作数栈中
30 (0x1e) -> 33 (0x21) lload_<n> 通过索引<n>从局部变量表加载long 类型数据到操作数栈中
34 (0x22) -> 37 (0x25) fload_<n> 通过索引<n>从局部变量表加载float 类型数据到操作数栈中
38 (0x26) -> 41 (0x29) dload_<n> 通过索引<n>从局部变量表加载double 类型数据到操作数栈中
42 (0x2a) -> 45 (0x2d) aload_<n> 通过索引<n>从局部变量表加载reference 类型数据到操作数栈中
46 (0x2e) iaload 根据下标从int类型数组中加载数据到操作数栈中
47 (0x2f) laload 根据下标从long类型数组中加载数据到操作数栈中
48 (0x30) faload 根据下标从float类型数组中加载数据到操作数栈中
49 (0x31) daload 根据下标从double类型数组中加载数据到操作数栈中
50 (0x32) aaload 根据下标从reference类型数组中加载数据到操作数栈中
51 (0x33) baload 根据下标从byteboolean类型数组中加载数据到操作数栈中
52 (0x34) caload 根据下标从char类型数组中加载数据到操作数栈中
53 (0x35) saload 根据下标从short类型数组中加载数据到操作数栈中

4.1 iload

4.2 lload

4.3 fload

4.4 dload

4.5 aload

4.6 iload_<n>

4.7 lload_<n>

4.8 fload_<n>

4.9 dload_<n>

4.10 aload_<n>

4.11 iaload

4.12 laload

4.13 faload

4.14 daload

4.15 aaload

4.15 baload

4.16 caload

4.17 saload

4.18 小结

Loads 类型指令分为两类:

Loads 类型指令执行之后,操作数栈栈顶会多一个数据value

五. Stores 类型指令

Stores 类型指令包括将操作数栈栈顶数据保存到局部变量表(local variables) 中,或者保存到数组对应下标位置中。

  • Stores 类型指令和Loads 类型指令是一一对应的。
  • Loads 类型指令是将局部变量表或者数组中的数据加载到操作数栈栈顶;执行之后操作数栈栈顶多一个数据value
  • Stores 类型指令是将操作数栈栈顶数据出栈,保存到局部变量表或者数组对应下标位置中;执行之后操作数栈会减少一个数据。

保存到局部变量表:

操作码 助记符 作用
54 (0x36) istore 将操作数栈栈顶int类型数据保存到局部变量表对应索引处
55 (0x37) lstore 将操作数栈栈顶long类型数据保存到局部变量表对应索引处
56 (0x38) fstore 将操作数栈栈顶float类型数据保存到局部变量表对应索引处
57 (0x39) dstore 将操作数栈栈顶double类型数据保存到局部变量表对应索引处
58 (0x3a) astore 将操作数栈栈顶reference类型数据保存到局部变量表对应索引处
59 (0x3b) -> 62 (0x3e) istore_<n> 将操作数栈栈顶int类型数据保存到局部变量表索引<n>
63 (0x3f) -> 66 (0x42) lstore_<n> 将操作数栈栈顶long类型数据保存到局部变量表索引<n>
67 (0x43) -> 70 (0x46) fstore_<n> 将操作数栈栈顶float类型数据保存到局部变量表索引<n>
71 (0x47) -> 74 (0x4a) dstore_<n> 将操作数栈栈顶double类型数据保存到局部变量表索引<n>
75 (0x4b) -> 78 (0x4e) astore_<n> 将操作数栈栈顶reference类型数据保存到局部变量表索引<n>
79 (0x4f) iastore 将操作数栈栈顶int类型数据保存到数组对应下标位置中
80 (0x50) lastore 将操作数栈栈顶long类型数据保存到数组对应下标位置中
81 (0x51) fastore 将操作数栈栈顶float类型数据保存到数组对应下标位置中
82 (0x52) dastore 将操作数栈栈顶double类型数据保存到数组对应下标位置中
83 (0x53) aastore 将操作数栈栈顶reference类型数据保存到数组对应下标位置中
84 (0x54) bastore 将操作数栈栈顶byteboolean类型数据保存到数组对应下标位置中
85 (0x55) castore 将操作数栈栈顶char类型数据保存到数组对应下标位置中
86 (0x56) sastore 将操作数栈栈顶short类型数据保存到数组对应下标位置中

例子

    public void test() {
        int i1 = 0;
        int i2 = i1;

        long l1 = 0;
        long l2 = l1;

        float f1 = 0;
        float f2 = f1;

        double d1 = 0;
        double d2 = d1;

        Object obj1 = null;
        Object obj2 = obj1;

        int[] ints = new int[10];
        ints[0] = 0;
        ints[1] = ints[0];

        boolean[] booleans = new boolean[10];
        booleans[0] = false;
        booleans[1] = booleans[0];
    }

对应字节码

 0 iconst_0
 1 istore_1
 2 iload_1
 3 istore_2
 4 lconst_0
 5 lstore_3
 6 lload_3
 7 lstore 5
 9 fconst_0
10 fstore 7
12 fload 7
14 fstore 8
16 dconst_0
17 dstore 9
19 dload 9
21 dstore 11
23 aconst_null
24 astore 13
26 aload 13
28 astore 14
30 bipush 10
32 newarray 10 (int)
34 astore 15
36 aload 15
38 iconst_0
39 iconst_0
40 iastore
41 aload 15
43 iconst_1
44 aload 15
46 iconst_0
47 iaload
48 iastore
49 bipush 10
51 newarray 4 (boolean)
53 astore 16
55 aload 16
57 iconst_0
58 iconst_0
59 bastore
60 aload 16
62 iconst_1
63 aload 16
65 iconst_0
66 baload
67 bastore
68 return

仔细分析字节码,你会看到发现:

  • int i1 = 0 对应的就是 0 iconst_0istore_1,将 0 保存到局部变量表1 索引处(因为这是一个实例方法,索引0处是当前方法对应的实例引用this)。
  • int i2 = i1 对应的就是iload_1istore_2,先将局部变量表1 索引处出数据加载到内存,然后再保存到局部变量表2 索引处。
  • long l2 = l1 对应的是 lload_3lstore 5。为什么会变成 5,那是因为 long 类型占据两个局部变量。
  • 最后讲解 booleans[0] = false,对应的是 aload 16,iconst_0,iconst_0bastore。先加载数组引用,第一个iconst_0表示数组下标,第二个iconst_0就表示 boolean 类型的 false,最后通过bastore指令将数据保存到数组中。

六. Stack 类型指令

Stack 类型指令都直接对操作数栈进行操作。

使用 Stack 类型指令时,一定要知道此时操作数栈中数据的种类。

我们将操作数栈中数据分为两个种类:

操作码 助记符 作用
87 (0x57) pop 将栈顶一个槽的数据出栈
88 (0x58) pop2 将栈顶两个槽的数据出栈
89 (0x59) dup 复制栈顶一个槽的数据,并插入栈顶
90 (0x5a) dup_x1 复制栈顶一个槽的数据,并插入栈顶以下两个槽之后
91 (0x5b) dup_x2 复制栈顶一个槽的数据,并插入栈顶以下三个槽之后
92 (0x5c) (dup2) dup2 复制栈顶两个槽的数据,并插入栈顶
93 (0x5d) dup2_x1 复制栈顶一个槽的数据,并插入栈顶以下三个槽之后
94 (0x5e) dup2_x2 复制栈顶一个槽的数据,并插入栈顶以下四个槽之后

6.1 pop

6.2 pop2

6.3 dup

6.4 dup_x1

6.5 dup_x2

6.6 dup2

6.7 dup2_x1

6.8 dup2_x2

6.9 swap

6.10 小结

Stack 类型指令对操作数栈操作,一共分为三种类型, 出栈,复制和交换;而且为了处理longdouble 这样占据两个槽的数据,提供了不同的指令。

七. Math 类型指令

Math 类型指令都是进行算术运算的。

操作码 助记符 作用
96 (0x60) --> 99 (0x63) <i,l,f,d>_add 将栈顶两个对应类型数据相加,再将结果值入栈
100 (0x64) --> 103 (0x67) <i,l,f,d>_sub 将栈顶两个对应类型数据相减,再将结果值入栈
104 (0x68)--> 107 (0x6b) <i,l,f,d>_mul 将栈顶两个对应类型数据相乘,再将结果值入栈
108 (0x6c) --> 111 (0x7f) <i,l,f,d>_div 将栈顶两个对应类型数据相除,再将结果值入栈
112 (0x70) --> 115 (0x73) <i,l,f,d>_rem : 将栈顶两个对应类型数据求余,再将结果值入栈
116 (0x74) --> 119 (0x77) <i,l,f,d>_neg 将栈顶对应类型数据进行取负运算(即-value),再将结果值入栈
120 (0x78)--> 121 (0x79) <i,l>_ shl 将值进行左移运算
122 (0x7a)--> 123 (0x7b) <i,l>_ shr 将值进行有符号右移运算
124 (0x7c)--> 125 (0x7d) <i,l>_ ushr 将值进行无符号右移运算
126 (0x7e) --> 127 (0x7f) <i,l>_and 对值进行按位与运算
128 (0x80) --> 129 (0x81) <i,l>_or 对值进行按位或运算
130 (0x82) --> 131 (0x83) <i,l>_xor 对值进行按位异或运算
132 (0x84) iinc 直接将局部变量表对应索引处的值增加值

7.1 <i,l,f,d>_add

7.2 <i,l,f,d>_sub

7.3 <i,l,f,d>_mul

7.4 <i,l,f,d>_div

7.5 <i,l,f,d>_rem

7.6 <i,l,f,d>_neg

7.7 ishl

7.8 lshl

7.9 ishr

7.10 lshr

7.11 <i,l>_ushr

7.12 <i,l>_and

7.13 <i,l>_or

7.14 <i,l>_xor

7.15 iinc

7.16 小结

大部分 Math 类型指令都是对栈中两个数进行运算,再将结果值入栈

..., value1, value2 →
..., result

只有两个类型指令不一样,那就是 <i,l,f,d>_negiinc

八. Conversions 类型指令

Conversions 类型指令是用来执行数值类型转换的。
Conversions 类型指令只有一个操作码,没有操作数;对操作数栈的变化是

  ..., value →
  ..., result

数值类型转换分为两种:

转换的规则大体都遵守上面的原则,但是byte,charshort 这三个类型需要注意:

例子

 public void test() {
        int i = Short.MAX_VALUE + 2;
        byte b = (byte) i;
        short s = (short) i;
        char c = (char) i;
        System.out.println(i+"   "+Integer.toBinaryString(i));
        System.out.println(b+"   "+Integer.toBinaryString(b));
        System.out.println(s+"   "+Integer.toBinaryString(s));
        System.out.println((int)c+"   "+Integer.toBinaryString(c));
        System.out.println("=====================");
        i = -12312321;
        b = (byte) i;
        s = (short) i;
        c = (char) i;
        System.out.println(i+"   "+Integer.toBinaryString(i));
        System.out.println(b+"   "+Integer.toBinaryString(b));
        System.out.println(s+"   "+Integer.toBinaryString(s));
        System.out.println((int)c+"   "+Integer.toBinaryString(c));
    }

运算结果

32769   1000000000000001
1   1
-32767   11111111111111111000000000000001
32769   1000000000000001
=====================
-12312321   11111111010001000010000011111111
-1   11111111111111111111111111111111
8447   10000011111111
8447   10000011111111
操作码 助记符 作用
133 (0x85) i2l int 类型转换成long 类型
134 (0x86) i2f int 类型转换成float 类型
135 (0x87) i2d int 类型转换成double 类型
136 (0x88) l2i long 类型转换成int 类型
137 (0x89) l2f long 类型转换成float 类型
138 (0x8a) l2d long 类型转换成double 类型
139 (0x8b) f2i float 类型转换成int 类型
140 (0x8c) f2l float 类型转换成long 类型
141 (0x8d) f2d float 类型转换成double 类型
142 (0x8e) d2i double 类型转换成int 类型
133 (0x8f) d2l double 类型转换成long 类型
144 (0x90) d2f double 类型转换成float 类型
145 (0x91) i2b int 类型转换成byte 类型
146 (0x92) i2c int 类型转换成char 类型
147 (0x93) i2s int 类型转换成short 类型

九. Comparisons 类型指令

Comparisons 类型指令是用来进行逻辑运算的。

操作码 助记符 作用
148 (0x94) lcmp 比较两个 long 类型的大小
149 (0x95) -> 150 (0x96) fcmp<op> 比较两个 float 类型的大小
151 (0x97) -> 152 (0x98) dcmp<op> 比较两个 double 类型的大小
153 (0x99) -> 158 (0x9e) if<cond> int数据与零比较的条件分支判断
159 (0x9f) -> 164 (0xa4) if_icmp<cond> int类型比较的条件分支判断
165 (0xa5) -> 166 (0xa6) if_acmp<cond> reference类型比较的条件分支判断

9.1 lcmp

9.2 fcmp<op>

9.3 dcmp<op>

9.3 if<cond>

9.4 if_icmp<cond>

9.5 if_acmp<cond>

9.6 小结

Comparisons 类型指令还是比较简单的,主要分为以下几类:

9.7 例子

    public void test() {
        long l = 0;
        if (l > 1) {
            l ++;
        } else {
            l --;
        }
    }

字节码

 //  long l = 0;
0 lconst_0
 1 lstore_1

 // l > 1
 2 lload_1
 3 lconst_1
 4 lcmp

 5 ifle 15 (+10)  // if判断,如果小于等于0,那么就跳转到 `15` 进行 l--
 //   l ++ 
 8 lload_1
 9 lconst_1
10 ladd
11 lstore_1
12 goto 19 (+7)   // 跳转到 return ,返回

 //   l -- 
15 lload_1
16 lconst_1
17 lsub
18 lstore_1
19 return

十一. Control 类型指令

Control 类型指令都是程序跳转的指令。

操作码 助记符 作用
167 (0xa7) goto 无条件分支跳转
168 (0xa8) jsr 程序段落跳转
169 (0xa9) ret 代码片段中返回
170 (0xaa) tableswitch 根据索引值在跳转表中寻找匹配的分支进行跳转
171 (0xab) lookupswitch 根据键值在跳转表中寻找匹配的分支进行跳转
172 (0xac) -> 176 (0xb0) <i,l,f,d,a>_return 从当前方法带返回值返回调用处
177 (0xb1) return 从当前方法直接返回调用处,不携带返回值

10.1 goto

10.2 jsr, jsr_wret

这三个指令已经基本上不使用了。

10.3 tableswitch

10.4 lookupswitch

10.5 <i,l,f,d,a>_return

10.6 return

十一. References 类型指令

References 类型指令是对引用类型进行操作的指令集。

操作码 助记符 作用
178 (0xb2) getstatic 获取静态属性的值入栈
179 (0xb3) putstatic 将操作数栈栈顶数据存入静态属性
180 (0xb4) getfield 获取实例属性的值入栈
181 (0xb5) putfield 将操作数栈栈顶数据存入实例属性
182 (0xb6) invokevirtual 用于调用所有的虚方法(不包括实例构造器,私有方法和父类中的方法)
183 (0xb7) invokespecial 用于调用实例构造器<init>()方法、私有方法和父类中的方法
184 (0xb8) invokestatic 用于调用静态方法
185 (0xb9) invokeinterface 用于调用接口方法
186 (0xba) invokedynamic 先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法
187 (0xbb) new 创建一个实例对象并入栈
188 (0xbc) newarray 创建一个原始类型数组实例对象并入栈
189 (0xbd) anewarray 创建一个引用类型数组实例对象并入栈
190 (0xbe) arraylength 获取数组长度并入栈
191 (0xbf) athrow 抛出一个 exception 或者 error
192 (0xc0) checkcast 进行引用类型类型转换
193 (0xc1) instanceof 判断对象是不是指定类型

11.1 getstatic

11.2 putstatic

11.3 getfield

11.4 putfield

11.5 invokevirtual

11.6 invokespecial

11.7 invokestatic

11.8 invokeinterface

11.9 invokedynamic

11.10 new

11.11 newarray

11.12 anewarray

11.13 arraylength

11.14 athrow

11.15 checkcast

11.16 instanceof

11.17 monitorentermonitorexit

十二. Extended 类型指令

Extended 类型指令是扩展指令。

操作码 助记符 作用
196 (0xc4) wide 通过附加的字节扩展来扩展局部变量表索引
197 (0xc5) multianewarray 创建多维数组
198 (0xc6) ifnull 判断是否为null 进行条件跳转
199 (0xc7) ifnonnull 判断是否不为null 进行条件跳转
200 (0xc8) goto_w 无条件分支跳转
201 (0xc9) jsr_w 程序段落跳转

12.1 wide

这个指令主要是用来针对局部变量表(local variables)索引的。

  • 我们知道局部变量表的长度最多时,两个字节无符号数的最大值。
  • 但是大部分情况下,局部变量表的长度都是蛮小的,不会超过一个字节无符号数。
  • 因此我们之前学到的 <>_load<>_store 从局部变量表中加载和存储数据,操作数都是只有一个字节;还有就是 iinc 指令,直接在局部变量进行加法操作,也只有一个字节。
  • 所以当局部变量表超过一个字节无符号数时,这些命令就不行了。就需要使用 wide 指令进行扩展了。

12.2 multianewarray

12.3 ifnull

12.4 ifnonnull

11.5 goto_w

上一篇 下一篇

猜你喜欢

热点阅读