JVM详解_指令集

一. 前置内容

本篇文章是对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 数据就失效了。

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

  • 第一种是方法的参数,会将方法定义的参数依次存储到局部变量表中。
    • 但是需要注意,如果是实例方法,第一个局部变量(即索引为0)一定用来存储该实例方法所属对象的引用(就是java 中的 this),然后再将方法参数依次从索引为 1 的位置开始存储。
    • 如果是静态方法(static 方法),那么就将方法参数依次从索引为 0 的位置开始存储。
  • 第二种就是方法中定义的局部变量。

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

  • 指令数据
    0 (0x0) (nop)
  • 操作数栈变化
    没有任何变化
    
  • 作用: nop 指令不做任何事情。

3.2 aconst_null

  • 指令数据
    1 (0x1) (aconst_null)
  • 操作数栈变化
    ... →
    ..., null
    
  • 作用: 将常量 null 存入操作数栈。

3.3 iconst_<i>

  • 指令数据
    iconst_<i>
  • 操作数栈变化
    ...  →
    ...,  <i>
    
  • 包括指令
    iconst_m1 = 2 (0x2)
    iconst_0 = 3 (0x3)
    iconst_1 = 4 (0x4)
    iconst_2 = 5 (0x5)
    iconst_3 = 6 (0x6)
    iconst_4 = 7 (0x7)
    iconst_5 = 8 (0x8)
    
  • 作用: 将int类型常量 -1,0,1,2,3,45 放入操作数栈中。
    • 为这几个int 常量设置专门的操作码,这样就不用使用操作数,也减少字节码大小。
    • 也可以使用 bipush 操作码来实现,但是会增加操作数。
  • 例子
        public void test() {
         int i = -1;
         i = 1;
         i = 2;
         i = 3;
         i = 4;
         i = 5;
         i = 6;
     }
    
    查看字节码
    0 iconst_m1
    1 istore_1
    2 iconst_1
    3 istore_1
    4 iconst_2
    5 istore_1
    6 iconst_3
    7 istore_1
    8 iconst_4
    9 istore_1
    10 iconst_5
    11 istore_1
    12 bipush 6
    14 istore_1
    15 return
    
    • istore 的指令后面再介绍,你会发现等到 6 之后,就使用了 bipush 6 指令了。
    • 而且你会发现使用 iconst_<i> 指令时,字节码地址每次都是增加一,而使用 bipush 6 指令,字节码地址增加了二,从 12 变成了 14

3.4 lconst_<l>

  • 指令数据
    lconst_<l>
  • 操作数栈变化
    ...  →
    ...,  <l>
    
  • 包括指令
    lconst_0 = 9 (0x9)
    lconst_1 = 10 (0xa)
    
  • 作用: 将long类型常量01 放入操作数栈中。

    它的意义和 iconst_<i> 一样。

3.5 fconst_<f>

  • 指令数据
    fconst_<f>
  • 操作数栈变化
    ...  →
    ...,  <f>
    
  • 包括指令
    fconst_0 = 11 (0xb)
    fconst_1 = 12 (0xc)
    fconst_2 = 13 (0xd)
    
  • 作用: 将float类型常量0.0,1.02.0 放入操作数栈中。

    它的意义和 iconst_<i> 一样。

3.6 dconst_<d>

  • 指令数据
    dconst_<d>
  • 操作数栈变化
    ...  →
    ...,  <d>
    
  • 包括指令
    dconst_0 = 14 (0xe)
    dconst_1 = 15 (0xf)
    
  • 作用: 将double类型常量0.01.0 放入操作数栈中。

    它的意义和 iconst_<i> 一样。

3.7 bipush

  • 指令数据
    16 (0x10) (bipush)
    byte
  • 操作数栈变化
    ... →
    ..., value
    
  • 作用: 将一个字节二进制数带符号扩展成一个 int 类型值 value,放入操作数栈中。

    因为 byte 是带符号的一个字节二进制数,范围就是 -128 -> 127

3.7 sipush

  • 指令数据
    17 (0x11) (sipush)
    byte1
    byte2
  • 操作数栈变化
    ... →
    ..., value
    
  • 作用: 将两个字节二进制数带符号扩展成一个 int 类型值 value,放入操作数栈中。

    因为 byte 是有符号的二个字节二进制数,范围就是 -32768 -> 32767

3.8 ldc

  • 指令数据
    18 (0x12) (ldc)
    index
  • 操作数栈变化
    ... →
    ..., value
    
  • 作用:通过索引将常量池中的 int,float,reference类型常量放入操作数栈中。
    • index 表示一个无符号二进制数,表示一个常量池索引。
    • 这个索引对应的常量应该是 int,floatreference 类型。

3.9 ldc_w

  • 指令数据
    19 (0x13) (ldc_w)
    indexbyte1
    indexbyte2
  • 操作数栈变化
    ... →
    ..., value
    
  • 作用:通过索引将常量池中的 int,float,reference类型常量放入操作数栈中。
    • 这个指令与 ldc 作用是相同的,只不过这个指令是获取两个无符号二进制数索引对应的常量。
    • class 字节码文件中,一个 class 类最多拥有65536 个常量,也就是两个无符号二进制数的最大值。

3.10 ldc2_w

  • 指令数据
    20 (0x14) (ldc2_w)
    indexbyte1
    indexbyte2
  • 操作数栈变化
    ... →
    ..., value
    
  • 作用:通过索引将常量池中的 longdouble 类型常量放入操作数栈中。
    • 这个指令与ldc_w 指令差不多,只不过它获取的是 longdouble 类型常量。

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) 中加载数据到操作数栈中,以及从数组中加载数据到操作数栈。

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

  • 指令数据
    <i,l,f,d,a>_load
    index
    或者
    <i,l,f,d,a>_load_<n>
  • 操作数栈变化
    ... →
    ..., value
    
  • 通过索引从局部变量表加载数据到操作数栈中。
  • 操作数栈栈顶会多一个数据。

从数组中加载数据:

  • 指令数据
    <i,l,f,d,a,b,c,s>_aload
  • 操作数栈变化
    ..., arrayref, index →
    ..., value
    
  • 通过数组引用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

  • 指令数据
    21 (0x15) (iload)
    index
  • 操作数栈变化
    ... →
    ..., value
    
  • 作用: 通过一个字节无符号索引index 从局部变量表中获取 int 类型数据value,放入操作数栈中。

4.2 lload

  • 指令数据
    22 (0x16) (lload)
    index
  • 操作数栈变化
    ... →
    ..., value
    
  • 作用: 通过一个字节无符号索引index 从局部变量表中获取 long 类型数据value,放入操作数栈中。

    因为一个 long 类型在局部变量表中占据两个局部变量,其实是通过indexindex + 1 这两处局部变量数值拼成一个long 类型数据。

4.3 fload

  • 指令数据
    23 ((0x17) (fload)
    index
  • 操作数栈变化
    ... →
    ..., value
    
  • 作用: 通过一个字节无符号索引index 从局部变量表中获取 float 类型数据value,放入操作数栈中。

4.4 dload

  • 指令数据
    24 (0x18) (dload)
    index
  • 操作数栈变化
    ... →
    ..., value
    
  • 作用: 通过一个字节无符号索引index 从局部变量表中获取 double 类型数据value,放入操作数栈中。

4.5 aload

  • 指令数据
    25 (0x19) (aload)
    index
  • 操作数栈变化
    ... →
    ..., objectref
    
  • 作用: 通过一个字节无符号索引index 从局部变量表中获取 reference 类型数据objectref,放入操作数栈中。

4.6 iload_<n>

  • 指令数据
    iload_<n>
  • 操作数栈变化
    ... →
    ..., value
    
  • 包括指令
    iload_0 = 26 (0x1a)
    iload_1 = 27 (0x1b)
    iload_2 = 28 (0x1c)
    iload_3 = 29 (0x1d)
    
  • 作用: 通过索引<n>(包括0,1,2,3)从局部变量表中获取 int 类型数据value,放入操作数栈中。

4.7 lload_<n>

  • 指令数据
    lload_<n>
  • 操作数栈变化
    ... →
    ..., value
    
  • 包括指令
    lload_0 = 30 (0x1e)
    lload_1 = 31 (0x1f)
    lload_2 = 32 (0x20)
    lload_3 = 33 (0x21)
    
  • 作用: 通过索引<n>(包括0,1,2,3)从局部变量表中获取 long 类型数据value,放入操作数栈中。

4.8 fload_<n>

  • 指令数据
    fload_<n>
  • 操作数栈变化
    ... →
    ..., value
    
  • 包括指令
    fload_0 = 34 (0x22)
    fload_1 = 35 (0x23)
    fload_2 = 36 (0x24)
    fload_3 = 37 (0x25)
    
  • 作用: 通过索引<n>(包括0,1,2,3)从局部变量表中获取 float 类型数据value,放入操作数栈中。

4.9 dload_<n>

  • 指令数据
    dload_<n>
  • 操作数栈变化
    ... →
    ..., value
    
  • 包括指令
    dload_0 = 38 (0x26)
    dload_1 = 39 (0x27)
    dload_2 = 40 (0x28)
    dload_3 = 41 (0x29)
    
  • 作用: 通过索引<n>(包括0,1,2,3)从局部变量表中获取 double 类型数据value,放入操作数栈中。

4.10 aload_<n>

  • 指令数据
    aload_<n>
  • 操作数栈变化
    ... →
    ..., objectref
    
  • 包括指令
    aload_0 = 42 (0x2a)
    aload_1 = 43 (0x2b)
    aload_2 = 44 (0x2c)
    aload_3 = 45 (0x2d)
    
  • 作用: 通过索引<n>(包括0,1,2,3)从局部变量表中获取 reference 类型数据value,放入操作数栈中。

4.11 iaload

  • 指令数据
    46 (0x2e) (iaload)
  • 操作数栈变化
    ..., arrayref, index →
    ..., value
    
  • 作用: 从int类型数组arrayref 中,根据下标index, 获取int 类型数据value,放入操作数栈中。

    index 必须是int 类型,数组的下标只能是int 类型。

4.12 laload

  • 指令数据
    47 (0x2f) (laload)
  • 操作数栈变化
    ..., arrayref, index →
    ..., value
    
  • 作用: 从long类型数组arrayref 中,根据下标index, 获取long 类型数据value,放入操作数栈中。

4.13 faload

  • 指令数据
    48 (0x30) (faload)
  • 操作数栈变化
    ..., arrayref, index →
    ..., value
    
  • 作用: 从float类型数组arrayref 中,根据下标index, 获取float 类型数据value,放入操作数栈中。

4.14 daload

  • 指令数据
    49 (0x31) (daload)
  • 操作数栈变化
    ..., arrayref, index →
    ..., value
    
  • 作用: 从double类型数组arrayref 中,根据下标index, 获取double 类型数据value,放入操作数栈中。

4.15 aaload

  • 指令数据
    50 (0x32) (aaload)
  • 操作数栈变化
    ..., arrayref, index →
    ..., value
    
  • 作用: 从reference类型数组arrayref 中,根据下标index, 获取reference 类型数据value,放入操作数栈中。

4.15 baload

  • 指令数据
    51 (0x33) (baload)
  • 操作数栈变化
    ..., arrayref, index →
    ..., value
    
  • 作用: 从byteboolean类型数组arrayref 中,根据下标index, 获取byteboolean类型数据value,放入操作数栈中。

4.16 caload

  • 指令数据
    52 (0x34) (caload)
  • 操作数栈变化
    ..., arrayref, index →
    ..., value
    
  • 作用: 从char类型数组arrayref 中,根据下标index, 获取char类型数据value,放入操作数栈中。

4.17 saload

  • 指令数据
    53 (0x35) (saload)
  • 操作数栈变化
    ..., arrayref, index →
    ..., value
    
  • 作用: 从short类型数组arrayref 中,根据下标index, 获取short类型数据value,放入操作数栈中。

4.18 小结

Loads 类型指令分为两类:

  • 从局部变量表(local variables)中加载数据。
    • 这类数据只有int,long,float,doublereference 数据类型。
    • 根据不同类型,助记符开始符号不一样;比如i 开头的和int 有关,a 开头的和 reference 有关。
  • 从数组中加载数据。
    • 首先这个数组引用,先通过 aload 指令从局部变量表中加载到操作数栈中,或者通过 ldcldc_w 从常量池中加载到操作数栈。
    • 数组是有八种类型的,除了上面五种还包括byte,charshort。这个和局部变量表中数据不一样,因为数组的数据是放在堆中的,允许创建这三种类型的数组。

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

五. Stores 类型指令

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

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

保存到局部变量表:

  • 指令数据
    <i,l,f,d,a>_store
    index
    或者
    <i,l,f,d,a>_store_<n>
  • 操作数栈变化
    ..., value →
    ...
    

    栈顶元素出栈,保存到局部变量表索引index处。

  • 保存到数组
    <i,l,f,d,a,b,c,s>_astore
    ..., arrayref, index, value →
    ...
    
    • 保存到数组的话,需要数组引用 arrayref ,数组下标index,和要保存的数据value
    • 将操作数栈中栈顶三个元素出栈。
操作码 助记符 作用
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 类型指令时,一定要知道此时操作数栈中数据的种类。

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

  • 第一种:包括int,floatreference,它们的特点是只使用一个槽的大小就可以存储。
  • 第一种:包括longdouble,它们的特点是必须使用两个槽的大小来存储。
操作码 助记符 作用
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

  • 指令数据
    87 (0x57) (pop)
  • 操作数栈变化
    ..., value →
    ...
    
  • 作用: 将栈顶的数据value出栈,这个value 必须是第一种类型。

6.2 pop2

  • 指令数据
    88 (0x58) (pop2)
  • 操作数栈变化
    第一种情况:
        ..., value2, value1 →
        ...
    
    第二种情况:
         ..., value →
         ...
    
  • 作用: 将栈顶的数据出栈,分为两种情况:
    • 栈顶有两个第一种类型的数据,直接将这两个数据value2value1 全部出栈。
    • 栈顶是第二种类型的数据,直接将这个数据 value出栈。
    • 不允许出现栈顶是第一种类型数据,而下面是第二种类型的数据;如果有这种情况,先用pop 指令将栈顶数据出栈。

6.3 dup

  • 指令数据
    89 (0x59) (dup)
  • 操作数栈变化
    ..., value →
    ..., value, value
    
  • 作用: 复制栈顶数据value,并插入栈顶;这个value 必须是第一种类型。

6.4 dup_x1

  • 指令数据
    90 (0x5a) (dup_x1)
  • 操作数栈变化
    ..., value2, value1 →
    ..., value1, value2, value1
    
  • 作用: 复制栈顶数据value1,并插入栈顶以下两个值之后;value1value2 都必须是第一种类型。

6.5 dup_x2

  • 指令数据
    91 (0x5b) (dup_x2)
  • 操作数栈变化
    第一种情况:
        ..., value3, value2, value1 →
        ..., value1, value3, value2, value1
    
    第二种情况:
         ..., value2, value1 →
         ..., value1, value2, value1
    
  • 作用: 复制栈顶数据value1,并插入栈顶以下两个值或三个值之后。
    • 第一种情况:value1,value2value3 都必须是第一种类型; 复制后的数据插入栈顶以下三个值之后。
    • 第二种情况:value1是第一种类型,value2 是第二种类型;复制后的数据插入栈顶以下两个值之后。
    • dup,dup_x1dup_x2 都是复制第一种类型数据的。

6.6 dup2

  • 指令数据
    92 (0x5c) (dup2)
  • 操作数栈变化
    第一种情况:
        ..., value2, value1 →
        ..., value2, value1, value2, value1
    
    第二种情况:
         ..., value →
         ..., value, value
    
  • 作用:
    • 第一种情况:value1value2 必须都是第一种类型,复制这两个数据并入栈。
    • 第二种情况:value 必须是第二种类型,复制这个数据并入栈。

6.7 dup2_x1

  • 指令数据
    93 (0x5d) (dup2_x1)
  • 操作数栈变化
    第一种情况:
        ..., value3, value2, value1 →
        ..., value2, value1, value3, value2, value1
    
    第二种情况:
         ..., value2, value1 →
         ..., value1, value2, value1
    
  • 作用:
    • 第一种情况:value1,value2value3 必须都是第一种类型,复制前两个数据value1value2插入到value3 下面。
    • 第二种情况:value1 必须是第二种类型,value2必须是第一种类型,复制栈顶数据value1插入到value2 下面。

6.8 dup2_x2

  • 指令数据
    94 (0x5e) (dup2_x2)
  • 操作数栈变化
    第一种情况:
        ..., value4, value3, value2, value1 →
        ..., value2, value1, value4, value3, value2, value1
    
    第二种情况:
         ..., value3, value2, value1 →
        ..., value1, value3, value2, value1
    
    第三种情况:
        ..., value3, value2, value1 →
        ..., value2, value1, value3, value2, value1
    
    第四种情况:
        ..., value2, value1 →
        ..., value1, value2, value1
    
  • 作用:
    • 第一种情况:value1,value2,value3value4 必须都是第一种类型,复制前两个数据value1value2插入到value4 下面。
    • 第二种情况:value1 是第二种类型,value2value3必须是第一种类型,复制栈顶数据value1插入到value3 下面。
    • 第二种情况:value1value2是第一种类型,value3是第二种类型,复制前两个数据value1value2插入到value3 下面。
    • 第四种情况:value1value2必须都是第二种类型,复制栈顶数据value1插入到value2 下面。

6.9 swap

  • 指令数据
    95 (0x5f) (swap)
  • 操作数栈变化
    ..., value2, value1 →
    ..., value1, value2
    ...
    
  • 作用: 将栈顶的两个数据value1value2 交换位置;value1value2 必须都是第一种类型,而且 JVM 没有提供第二种类型数据交换位置的指令。

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

  • 指令数据
    <i,l,f,d>_add
  • 操作数栈变化
    ..., value1, value2 →
    ..., result
    
  • 包括指令
    iadd = 96 (0x60)
    ladd = 97 (0x61)
    fadd = 98 (0x62)
    dadd = 99 (0x63)
    
  • 作用: 将栈顶两个对应类型数据相加,再将结果值入栈,结果值还是对应类型。

7.2 <i,l,f,d>_sub

  • 指令数据
    <i,l,f,d>_sub
  • 操作数栈变化
    ..., value1, value2 →
    ..., result
    
  • 包括指令
    isub = 100 (0x64)
    lsub = 101 (0x65)
    fsub = 102 (0x66)
    dsub = 103 (0x67)
    
  • 作用: 将栈顶两个对应类型数据相减,再将结果值入栈,结果值还是对应类型。

7.3 <i,l,f,d>_mul

  • 指令数据
    <i,l,f,d>_mul
  • 操作数栈变化
    ..., value1, value2 →
    ..., result
    
  • 包括指令
    imul= 104 (0x68)
    lmul = 105 (0x69)
    fmul = 106 (0x6a)
    dmul = 107 (0x6b)
    
  • 作用: 将栈顶两个对应类型数据相乘,再将结果值入栈,结果值还是对应类型。

7.4 <i,l,f,d>_div

  • 指令数据
    <i,l,f,d>_div
  • 操作数栈变化
    ..., value1, value2 →
    ..., result
    
  • 包括指令
    idiv= 108 (0x6c)
    ldiv = 109 (0x6d)
    fdiv = 110 (0x6e)
    ddiv = 111 (0x7f)
    
  • 作用: 将栈顶两个对应类型数据相除,再将结果值入栈,结果值还是对应类型。

7.5 <i,l,f,d>_rem

  • 指令数据
    <i,l,f,d>_rem
  • 操作数栈变化
    ..., value1, value2 →
    ..., result
    
  • 包括指令
    irem= 112 (0x70)
    lrem = 113 (0x71)
    frem = 114 (0x72)
    drem = 115 (0x73)
    
  • 作用: 将栈顶两个对应类型数据求余,再将结果值入栈,结果值还是对应类型。

    floatdouble 类型的求余,是会先经过数值集合转换成对应的 intlong 类型,进行求余,然后再转换为floatdouble 类型。

7.6 <i,l,f,d>_neg

  • 指令数据
    <i,l,f,d>_neg
  • 操作数栈变化
    ..., value →
    ..., result
    
  • 包括指令
    ineg= 116 (0x74)
    lneg= 117 (0x75)
    fneg= 118 (0x76)
    dneg= 119 (0x77)
    
  • 作用: 将栈顶对应类型数据进行取负运算(即-value),再将结果值入栈,结果值还是对应类型。

7.7 ishl

  • 指令数据
    120 (0x78) (ishl)
  • 操作数栈变化
    ..., value1, value2 →
    ..., result
    
  • 作用: 将int 类型的值进行左移运算。
    • value1value2 必须都是 int 类型,执行命令时,value1value2出栈,然后将 value1 左移 s 位(svalue25位的值),最后将结果值再入栈。
    • 因为 int 类型是 32 位数据,进行位移运算,范围就是 0 --> 31,正好可以用 5 个二进制数表示,所以这里的s 就是value25位的值。

7.8 lshl

  • 指令数据
    121 (0x79) (lshl)
  • 操作数栈变化
    ..., value1, value2 →
    ..., result
    
  • 作用: 将long 类型的值进行左移运算。
    • value1value2 必须都是 long 类型,执行命令时,value1value2出栈,然后将 value1 左移 s 位(svalue26位的值),最后将结果值再入栈。
    • 因为 long 类型是 64 位数据,进行位移运算,范围就是 0 --> 63,正好可以用 6 个二进制数表示,所以这里的s 就是value26位的值。

7.9 ishr

  • 指令数据
    122 (0x7a) (ishr)
  • 操作数栈变化
    ..., value1, value2 →
    ..., result
    
  • 作用: 将int 类型的值进行右移运算。
    • value1value2 必须都是 int 类型,执行命令时,value1value2出栈,然后将 value1 右移 s 位(svalue25位的值),最后将结果值再入栈。
    • 因为 int 类型是 32 位数据,进行位移运算,范围就是 0 --> 31,正好可以用 5 个二进制数表示,所以这里的s 就是value25位的值。

7.10 lshr

  • 指令数据
    123 (0x7b) (lshr)
  • 操作数栈变化
    ..., value1, value2 →
    ..., result
    
  • 作用: 将long 类型的值进行右移运算。
    • value1value2 必须都是 long 类型,执行命令时,value1value2出栈,然后将 value1 右移 s 位(svalue26位的值),最后将结果值再入栈。
    • 因为 long 类型是 64 位数据,进行位移运算,范围就是 0 --> 63,正好可以用 6 个二进制数表示,所以这里的s 就是value26位的值。

7.11 <i,l>_ushr

  • 指令数据
    <i,l>_ushr
  • 操作数栈变化
    ..., value1, value2 →
    ..., result
    
  • 包括指令
    124 (0x7c) iushr
    125 (0x7d) lushr
    
  • 作用: 将intlong 类型的值进行无符号右移运算,即用高位补零的方式进行右移。
    • 我们知道正数的最高位是 0,负数的最高位是 1;我们进行右移运算时,原来的最高位会右移一位,那么新的最高位补什么呢?
    • 如果使用<i,l>_shr 指令,新的最高位补就和原先最高位是一样的;也就是说正数是补零,负数就补1
    • 如果使用<i,l>_ushr 指令,不管是正数还是负数,新的最高位补只会是零。
    • 所以你会发现<i,l>_shr 指令,调用很多次后,最后结果值一定是 -1;而<i,l>_ushr 指令,调用很多次后,最后结果值一定是 0
  • 例子
    public void test() {
         int index = 0;
         int i = -62;
         while (index < 33) {
             System.out.println("index:"+index+"   i:"+i+"   " +Integer.toBinaryString(i));
             i = i >> 1;
             index++;
         }
    
    
         System.out.println("===============");
         System.out.println("===============");
    
          index = 0;
          i = -62;
          while (index < 33) {
              System.out.println("index:"+index+"   i:"+i+"   " +Integer.toBinaryString(i));
              i = i >>> 1;
              index++;
          }
      }
    
    运行结果:
    index:0   i:-62   11111111111111111111111111000010
    index:1   i:-31   11111111111111111111111111100001
    index:2   i:-16   11111111111111111111111111110000
    index:3   i:-8   11111111111111111111111111111000
    index:4   i:-4   11111111111111111111111111111100
    index:5   i:-2   11111111111111111111111111111110
    index:6   i:-1   11111111111111111111111111111111
    index:7   i:-1   11111111111111111111111111111111
    index:8   i:-1   11111111111111111111111111111111
    index:9   i:-1   11111111111111111111111111111111
    index:10   i:-1   11111111111111111111111111111111
    index:11   i:-1   11111111111111111111111111111111
    index:12   i:-1   11111111111111111111111111111111
    index:13   i:-1   11111111111111111111111111111111
    index:14   i:-1   11111111111111111111111111111111
    index:15   i:-1   11111111111111111111111111111111
    index:16   i:-1   11111111111111111111111111111111
    index:17   i:-1   11111111111111111111111111111111
    index:18   i:-1   11111111111111111111111111111111
    index:19   i:-1   11111111111111111111111111111111
    index:20   i:-1   11111111111111111111111111111111
    index:21   i:-1   11111111111111111111111111111111
    index:22   i:-1   11111111111111111111111111111111
    index:23   i:-1   11111111111111111111111111111111
    index:24   i:-1   11111111111111111111111111111111
    index:25   i:-1   11111111111111111111111111111111
    index:26   i:-1   11111111111111111111111111111111
    index:27   i:-1   11111111111111111111111111111111
    index:28   i:-1   11111111111111111111111111111111
    index:29   i:-1   11111111111111111111111111111111
    index:30   i:-1   11111111111111111111111111111111
    index:31   i:-1   11111111111111111111111111111111
    index:32   i:-1   11111111111111111111111111111111
    ===============
    ===============
    index:0   i:-62   11111111111111111111111111000010
    index:1   i:2147483617   1111111111111111111111111100001
    index:2   i:1073741808   111111111111111111111111110000
    index:3   i:536870904   11111111111111111111111111000
    index:4   i:268435452   1111111111111111111111111100
    index:5   i:134217726   111111111111111111111111110
    index:6   i:67108863   11111111111111111111111111
    index:7   i:33554431   1111111111111111111111111
    index:8   i:16777215   111111111111111111111111
    index:9   i:8388607   11111111111111111111111
    index:10   i:4194303   1111111111111111111111
    index:11   i:2097151   111111111111111111111
    index:12   i:1048575   11111111111111111111
    index:13   i:524287   1111111111111111111
    index:14   i:262143   111111111111111111
    index:15   i:131071   11111111111111111
    index:16   i:65535   1111111111111111
    index:17   i:32767   111111111111111
    index:18   i:16383   11111111111111
    index:19   i:8191   1111111111111
    index:20   i:4095   111111111111
    index:21   i:2047   11111111111
    index:22   i:1023   1111111111
    index:23   i:511   111111111
    index:24   i:255   11111111
    index:25   i:127   1111111
    index:26   i:63   111111
    index:27   i:31   11111
    index:28   i:15   1111
    index:29   i:7   111
    index:30   i:3   11
    index:31   i:1   1
    index:32   i:0   0
    

7.12 <i,l>_and

  • 指令数据
    <i,l>_and
  • 操作数栈变化
    ..., value1, value2 →
    ..., result
    
  • 包括指令
    126 (0x7e) iand
    127 (0x7f) land
    
  • 作用: 对 int 或者 long 进行按位与运算。

    value1value2 必须是同一种类型,结果值也是这种类型。

7.13 <i,l>_or

  • 指令数据
    <i,l>_or
  • 操作数栈变化
    ..., value1, value2 →
    ..., result
    
  • 包括指令
    128 (0x80) ior
    129 (0x81) lor
    
  • 作用: 对 int 或者 long 进行按位或运算。

    value1value2 必须是同一种类型,结果值也是这种类型。

7.14 <i,l>_xor

  • 指令数据
    <i,l>_xor
  • 操作数栈变化
    ..., value1, value2 →
    ..., result
    
  • 包括指令
    130 (0x82) ixor
    131 (0x83) lxor
    
  • 作用: 对 int 或者 long 进行按位异或运算。
    • 异或运算就是相同为0,不同为1;即 0 ^ 1 结果是 1, 而 0 ^ 0 或者 1 ^ 1 结果都是 0
    • 由于异或运算这个特性,位取反运算(~)就是通过这个异或-1 来实现的。因为 -1 的二进制表示1111....1 都是1,那么它和任何数异或,就相当于取反。
  • 例子
        public void test() {
          int i = 0;
          // 取反位运算
          i = ~i;
          // 异或位运算
          i = i ^ 1;
      }
    
    字节码:
     0 iconst_0
     1 istore_1
    
     // 取反位运算
     2 iload_1
     3 iconst_m1     // 异或 -1
     4 ixor
     5 istore_1
    
      // 异或位运算
     6 iload_1
     7 iconst_1
     8 ixor
     9 istore_1
    10 return
    

7.15 iinc

  • 指令数据
    132 (0x84) (iinc)
    index
    const
  • 操作数栈变化
    不会有任何变化
    
  • 作用: 直接将局部变量表中一个字节无符号索引值index 处的int 类型值,增加一个字节有符号数const 的大小。
    • 这个直接对局部变量表中数进行操作,一步操作就搞定;普通加法运算,是要先将局部变量表值加载到操作数栈中,进行加法运算,然后再将结果值存入局部变量表中。
    • const 是一个字节的有符号数,范围就是 -128 --> 127,也就是说JVM 是允许这个范围内的数,使用 iinc 指令。但是在java 只有int 类型的 ++-- 运算符能使用这个iinc 指令。
    • 特别注意,这个是对局部变量表的操作,所以实例中的int 类型属性和类的int 类型属性,即使用了 ++-- 运算符,也不是使用这个指令的。
  • 例子
        // 静态变量
      static int si = 0;
      // 实例变量
      int ci = 0;
      public void test() {
          // 局部变量
          int i = 0;
          // 使用 iinc 1 1
          i++;
          // 使用 iinc 1 -1
          i--;
          i = i + 1;
          
          // 都不使用 iinc 指令
          ci ++;
          si ++;
      }
    
    字节码:
     iconst_0
     1 istore_1
     2 iinc 1 by 1          //  i++
     5 iinc 1 by -1         //  i--
    
     //  i = i + 1 开始
     8 iload_1
     9 iconst_1
    10 iadd
    11 istore_1
      //  i = i + 1 结束
    
    // ci ++ 开始
    12 aload_0
    13 dup
    14 getfield #2 <com/zhang/jvm/test/T.ci>
    17 iconst_1
    18 iadd
    19 putfield #2 <com/zhang/jvm/test/T.ci>
     // ci ++ 结束
    
     // si ++ 开始
    22 getstatic #3 <com/zhang/jvm/test/T.si>
    25 iconst_1
    26 iadd
    27 putstatic #3 <com/zhang/jvm/test/T.si>
    // si ++ 结束
    30 return
    

7.16 小结

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

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

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

  • <i,l,f,d>_neg
    ..., value →
    ..., result
    
  • iinc 不会对操作数栈进行任何操作。

八. Conversions 类型指令

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

  ..., value →
  ..., result

数值类型转换分为两种:

  • 扩展了位数,例如 int转成 long,又 32 位变成64 位。

    这种都是带符号扩展,也就是说用原来最高位01 来填充新添加位的值。

  • 缩小了位数,例如 long转成 int,又 64 位变成32 位。

    这种就是直接截断,将多余的位数删掉,只保留缩小后的位数。这个就会导致转换后的数可能正负号不一样了。

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

  • byte,charshort 都会直接当成 int 类型使用
    • 一个字节的byte类型数据,会带符号扩展成 int 类型。
    • 两个字节的short类型数据,会带符号扩展成 int 类型。
    • 两个字节的char类型数据,会不带符号扩展成 int 类型。
  • 这个和java 语言中类型自动升级是不一样的
    • 例如 int i = 10; long l = i,不用写强转符号,但是编译成的字节码中,会使用 i2l 这个指令。
    • 但是 short s = 10; int i = (int)s; , 即使写了强转符号,编译成的字节码中,不会有什么shortint 指令,事实上也没有这个指令。
  • 只有 i2b,i2ci2s
    • i2b 截取一个字节,i2ci2s截取两个字节。
    • 又因为在 JVM 中,byte,charshort 这三个类型都是当 int 类型使用的,也就是说截取之后,又是当前 int 存储。
    • 这个时候,byteshort扩展成int 类型,是带符号扩展的,而char是不带符号扩展的,因为char肯定是正数或者0
  • 其他类型转换成 byte,charshort

    需要使用两个指令,先转换成 int 类型,再转换成这三种类型。

例子

 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

  • 指令数据
    148 (0x94) (lcmp)
  • 操作数栈变化
    ..., value1, value2 →
    ..., result
    
  • 作用: 比较两个 long 类型的大小
    • value1value2 必须都是 long 类型。
    • 如果value1 大于 value2,将int 类型数据 1 放入操作数栈。
    • 如果value1 等于 value2,将int 类型数据 0 放入操作数栈。
    • 如果value1 小于 value2,将int 类型数据 -1 放入操作数栈。

9.2 fcmp<op>

  • 指令数据
    fcmp<op>
  • 操作数栈变化
    ..., value1, value2 →
    ..., result
    
  • 包括指令
    fcmpg = 150 (0x96)
    fcmpl = 149 (0x95)
    
  • 作用:比较两个 float 类型的大小
    • value1value2 必须都是 float 类型。先通过数值集合转换得到value1'value2',开始进行比较:
    • 如果value1' 大于 value2',将int 类型数据 1 放入操作数栈。
    • 如果value1' 等于 value2',将int 类型数据 0 放入操作数栈。
    • 如果value1' 小于 value2',将int 类型数据 -1 放入操作数栈。
    • 如果 value1'value2' 有一个是 NaN,那么 fcmpg 命令是将 int 类型数据 1 放入操作数栈;而 fcmpl命令是将 int 类型数据 -1 放入操作数栈。

9.3 dcmp<op>

  • 指令数据
    dcmp<op>
  • 操作数栈变化
    ..., value1, value2 →
    ..., result
    
  • 包括指令
    dcmpg = 152 (0x98)
    dcmpl = 151 (0x97)
    
  • 作用:比较两个 double 类型的大小
    • value1value2 必须都是 double 类型。先通过数值集合转换得到value1'value2',开始进行比较:
    • 如果value1' 大于 value2',将int 类型数据 1 放入操作数栈。
    • 如果value1' 等于 value2',将int 类型数据 0 放入操作数栈。
    • 如果value1' 小于 value2',将int 类型数据 -1 放入操作数栈。
    • 如果 value1'value2' 有一个是 NaN,那么 dcmpg 命令是将 int 类型数据 1 放入操作数栈;而 dcmpl命令是将 int 类型数据 -1 放入操作数栈。

9.3 if<cond>

  • 指令数据
    dcmp<op>
    branchbyte1
    branchbyte2
  • 操作数栈变化
    ..., value →
    ...
    
  • 包括指令
    ifeq = 153 (0x99)
    ifne = 154 (0x9a)
    iflt = 155 (0x9b)
    ifge = 156 (0x9c)
    ifgt = 157 (0x9d)
    ifle = 158 (0x9e)
    
  • 作用: int数据与零比较的条件分支判断

    value 必须是 int 类型,然后与零进行比较:

    • ifeq succeeds if and only if value = 0
    • ifne succeeds if and only if value ≠ 0
    • iflt succeeds if and only if value < 0
    • ifle succeeds if and only if value ≤ 0
    • ifgt succeeds if and only if value > 0
    • ifge succeeds if and only if value ≥ 0
      如果比较结果为真,那么程序就跳转到由branchbyte1branchbyte2 构造的两个字节无符号指定地址执行,如果为假,那么就 继续执行 if<cond> 指令后面的其他指令。

9.4 if_icmp<cond>

  • 指令数据
    if_icmp<cond>
    branchbyte1
    branchbyte2
  • 操作数栈变化
    ..., value1, value2 →
    ...
    
  • 包括指令
    if_icmpeq = 159 (0x9f)
    if_icmpne = 160 (0xa0)
    if_icmplt = 161 (0xa1)
    if_icmpge = 162 (0xa2)
    if_icmpgt = 163 (0xa3)
    if_icmple = 164 (0xa4)
    
  • 作用: int类型比较的条件分支判断

    这个指令与if<cond> 指令的操作逻辑是一样的,if<cond> 指令就相当于 value2 为零的if_icmp<cond> 特殊格式。

    • if_icmpeq succeeds if and only if value1 = value2
    • if_icmpne succeeds if and only if value1 ≠ value2
    • if_icmplt succeeds if and only if value1 < value2
    • if_icmple succeeds if and only if value1 ≤ value2
    • if_icmpgt succeeds if and only if value1 > value2
    • if_icmpge succeeds if and only if value1 ≥ value2

9.5 if_acmp<cond>

  • 指令数据
    if_acmp<cond>
    branchbyte1
    branchbyte2
  • 操作数栈变化
    ..., value1, value2 →
    ...
    
  • 包括指令
    if_acmpeq = 165 (0xa5)
    if_acmpne = 166 (0xa6)
    
  • 作用: reference类型比较的条件分支判断

    reference类型只有相等和不等的条件判断,还有是否为 null 的条件判断,这个在Extended扩展类型指令中。

    • if_acmpeq succeeds if and only if value1 = value2
    • if_acmpne succeeds if and only if value1 ≠ value2

9.6 小结

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

  • long,floatdouble 类型
    它们的比较指令操作数栈变化如下
    ..., value1, value2 →
    ..., result
    
    • value1value2 必须是各自的类型。
    • 比较结果值 resultint 类型,如果大于就是 1,等于就是 0,小于就是 -1
    • floatdouble 比较特殊,有 NaN 值需要进行特殊处理。
    • 这几个比较指令,都不涉及程序跳转,只是将比较结果值入栈。
  • int 类型
    if<cond>if_icmp<cond> 都表示int类型比较的条件分支判断,只不过 if<cond> 只与零进行比较。
    它们的操作数栈变化
     ..., value1, value2 →  
     ...
    
    根据比较结果进行程序跳转。
  • reference 类型
    它只有相等和不等的条件判断。

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

  • 指令数据
    167 (0xa7) (goto)
    branchbyte1
    branchbyte2
  • 操作数栈变化
    没有任何变化
    ...
    
  • 作用: 无条件跳转到两个字节branchbyte1branchbyte2 组成的无符号指令地址处。

10.2 jsr, jsr_wret

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

10.3 tableswitch

  • 指令数据
    170 (0xaa) (tableswitch)
    <0-3 byte pad>
    defaultbyte1
    defaultbyte2
    defaultbyte3
    defaultbyte4
    lowbyte1
    lowbyte2
    lowbyte3
    lowbyte4
    highbyte1
    highbyte2
    highbyte3
    highbyte4
    jump offsets...
  • 操作数栈变化
    ..., index →
    ...
    ...
    
  • 作用: 根据索引值index在跳转表中寻找匹配的分支进行跳转。

10.4 lookupswitch

  • 指令数据
    171 (0xab) (lookupswitch)
    <0-3 byte pad>
    defaultbyte1
    defaultbyte2
    defaultbyte3
    defaultbyte4
    npairs1
    npairs2
    npairs3
    npairs4
    match-offset pairs...
  • 操作数栈变化
    ..., key →
    ...
    ...
    
  • 作用: 根据键值key在跳转表中寻找匹配的分支进行跳转。

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

  • 指令数据
    <i,l,f,d,a>_return
  • 操作数栈变化
    ..., value →
    [empty]
    ...
    
  • 包括指令
    172 (0xac) ireturn
    173 (0xad) lreturn
    174 (0xae) freturn
    175 (0xaf) dreturn
    176 (0xb0) areturn
    
  • 作用: 从当前方法带返回值value返回调用处。

10.6 return

  • 指令数据
    177 (0xb1) (return)
  • 操作数栈变化
    ... →
    [empty]
    ...
    
  • 作用: 从当前方法直接返回调用处,不携带返回值。

十一. 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

  • 指令数据
    178 (0xb2) (getstatic)
    indexbyte1
    indexbyte2
  • 操作数栈变化
    ..., →
    ..., value
    
  • 作用: 获取静态属性的值入栈。

    通过两个字节无符号数( (indexbyte1 << 8) | indexbyte2)索引从运行时常量池冲获取类的静态属性的引用。

11.2 putstatic

  • 指令数据
    179 (0xb3) (putstatic)
    indexbyte1
    indexbyte2
  • 操作数栈变化
    ..., value →
    ...
    
  • 作用: 将操作数栈栈顶数据value 存入静态属性中。

    通过两个字节无符号数( (indexbyte1 << 8) | indexbyte2)索引从运行时常量池冲获取类的静态属性的引用。

11.3 getfield

  • 指令数据
    180 (0xb4) (getfield)
    indexbyte1
    indexbyte2
  • 操作数栈变化
    ..., objectref →
    ..., value
    
  • 作用: 获取objectref实例属性的值入栈。

    通过两个字节无符号数( (indexbyte1 << 8) | indexbyte2)索引从运行时常量池冲获取objectref实例属性的引用。

11.4 putfield

  • 指令数据
    181 (0xb5) (putfield)
    indexbyte1
    indexbyte2
  • 操作数栈变化
    ..., objectref, value →
    ...
    
  • 作用: 将操作数栈栈顶数据value 存入objectref 对应的实例属性中。

    通过两个字节无符号数( (indexbyte1 << 8) | indexbyte2)索引从运行时常量池冲获取objectref实例属性的引用。

11.5 invokevirtual

  • 指令数据
    182 (0xb6) (invokevirtual)
    indexbyte1
    indexbyte2
  • 操作数栈变化
    ..., objectref, [arg1, [arg2 ...]] →
    ...
    
  • 作用: 用于调用所有的虚方法(不包括实例构造器,私有方法和父类中的方法)。

11.6 invokespecial

  • 指令数据
    183 (0xb7) (invokespecial)
    indexbyte1
    indexbyte2
  • 操作数栈变化
    ..., objectref, [arg1, [arg2 ...]] →
    ...
    
  • 作用: 用于调用实例构造器<init>()方法、私有方法和父类中的方法。

11.7 invokestatic

  • 指令数据
    184 (0xb8) (invokestatic)
    indexbyte1
    indexbyte2
  • 操作数栈变化
    ..., [arg1, [arg2 ...]] →
    ...
    
  • 作用: 用于调用静态方法。

11.8 invokeinterface

  • 指令数据
    185 (0xb9) (invokeinterface)
    indexbyte1
    indexbyte2
    count
    0
  • 操作数栈变化
    ..., objectref, [arg1, [arg2 ...]] →
    ...
    
  • 作用: 用于调用接口方法。

11.9 invokedynamic

  • 指令数据
    186 (0xba) (invokedynamic)
    indexbyte1
    indexbyte2
    0
    0
  • 操作数栈变化
    ..., [arg1, [arg2 ...]] →
    ...
    
  • 作用: 先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法。

11.10 new

  • 指令数据
    187 (0xbb) (new)
    indexbyte1
    indexbyte2
  • 操作数栈变化
    ... →
    ..., objectref
    
  • 作用: 创建一个实例对象并入栈。

11.11 newarray

  • 指令数据

    188 (0xbc) (newarray)
    atype
  • 操作数栈变化

    ..., count →
    ..., arrayref
    
  • 作用: 创建一个原始类型数组实例对象并入栈。

    count 表示创建数组的长度,必须是 int 类型。atype 代表数组中存放的原始类型:

    Array Type atype
    T_BOOLEAN 4
    T_CHAR 5
    T_FLOAT 6
    T_DOUBLE 7
    T_BYTE 8
    T_SHORT 9
    T_INT 10
    T_LONG 11

11.12 anewarray

  • 指令数据
    189 (0xbd) (anewarray)
    indexbyte1
    indexbyte2
  • 操作数栈变化
    ..., count →
    ..., arrayref
    
  • 作用: 创建一个引用类型数组实例对象并入栈。

11.13 arraylength

  • 指令数据
    190 (0xbe) (arraylength)
  • 操作数栈变化
    ..., arrayref →
    ..., length
    
  • 作用: 获取数组长度并入栈。

11.14 athrow

  • 指令数据
    191 (0xbf) (athrow)
  • 操作数栈变化
    ..., objectref →
    objectref
    
  • 作用: 抛出一个 exception 或者 error

11.15 checkcast

  • 指令数据
    192 (0xc0) (checkcast)
    indexbyte1
    indexbyte2
  • 操作数栈变化
    ..., objectref →
    ..., objectref
    
  • 作用: 进行引用类型类型转换。

11.16 instanceof

  • 指令数据
    193 (0xc1) (instanceof)
    indexbyte1
    indexbyte2
  • 操作数栈变化
    ..., objectref →
    ..., result
    
  • 作用: 判断对象是不是指定类型。

11.17 monitorentermonitorexit

  • 指令数据
    194 (0xc2) (monitorenter)
    195 (0xc3) (monitorexit)
  • 操作数栈变化
    ..., objectref →
    ...
    
  • 作用: 进入一个对象的监控(monitor) 和 退出一个对象的监控(monitor) 。

    使用这两个命令实现同步锁 synchronized

十二. 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 指令进行扩展了。
  • 指令数据
    类型一:

    wide
    <>_load<>_store
    indexbyte1
    indexbyte2

    类型二:

    wide
    iinc
    indexbyte1
    indexbyte2
    constbyte1
    constbyte1
  • 操作数栈变化

    和被扩展的指令一致
    

12.2 multianewarray

  • 指令数据
    197 (0xc5) (multianewarray)
  • 操作数栈变化
    ..., count1, [count2, ...] →
    ..., arrayref
    ...
    
  • 作用: 创建多维数组,并入栈。
  • 实例
     public void test() {
         int[][][] i3Arr = new int[3][3][3];
      }
    
    字节码
    0 iconst_3
    1 iconst_3
    2 iconst_3
    3 multianewarray #3 <[[[I> dim 3
    7 astore_1
    8 return
    

12.3 ifnull

  • 指令数据
    198 (0xc6) (ifnull)
    branchbyte1
    branchbyte2
  • 操作数栈变化
    ..., value →
    ...
    
  • 作用: 根据操作数栈栈顶值value是否为 null 进行条件跳转。

12.4 ifnonnull

  • 指令数据
    199 (0xc7) (ifnonnull)
    branchbyte1
    branchbyte2
  • 操作数栈变化
    ..., value →
    ...
    
  • 作用: 根据操作数栈栈顶值value是否不是 null 进行条件跳转。

11.5 goto_w

  • 指令数据
    200 (0xc8) (goto_w)
    branchbyte1
    branchbyte2
    branchbyte3
    branchbyte4
  • 操作数栈变化
    没有任何变化
    ...
    
  • 作用: 无条件跳转到四个字节组成的无符号指令地址处。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容