浅谈JVM Instruction Set (Opcode)

发布时间 2023-11-14 15:20:58作者: Ashiamd

浅谈JVM Instruction Set (Opcode)

1. 背景

日常开发中,遇到一个潜藏bug的java代码,借此简单回顾一下JVM Instruction Set (Opcode)知识。

问题demo代码如下:

public class BugDemo {

    public static void main(String[] args) {
        // 模拟用户输入(具有不可预测性), 假设用户输入null值
        Integer userInputNumber = null;
        System.out.println(DemoEnum.ENUM_01.enumEqual(userInputNumber));
    }

    public enum DemoEnum {
        ENUM_01;

        public boolean enumEqual(Integer number) {
            return number == this.ordinal();
        }
    }
}

这里并不会输出false,反而还会抛出一个 java.lang.NullPointerException 异常。

原因在于enumEqual方法的 return number == this.ordinal();,这里实际代码编译时,代码为return number.intValue() == this.ordinal()

显然number这里传入的参数为null,所以调用intValue()会导致NPE。

2. 自动装箱/拆箱代码示例 (Boxing/Unboxing Conversion)

Java对于基础类型都提供了相应的包装类型,前面提到的Integer即为基础类型int对应的包装类型。

这里先不谈什么情况会发生自动装箱/拆箱(大伙可以自行查阅资料,后续文章中也会提供相关资料),一起先看看如下代码(大伙可以想象):

import java.util.Objects;

public class BoxingTest {

    public static void main(String[] args) {
        Integer integerValue = Integer.valueOf(128);
        int intValue = 128;
        boolean b1 = integerValue == intValue;
        boolean b2 = intValue == integerValue;
        boolean b3 = Objects.equals(integerValue, intValue);
        boolean b4 = Objects.equals(intValue, integerValue);
        boolean b5 = integerValue.equals(intValue);
        integerValue = null;
        boolean b6 = Objects.equals(integerValue, intValue);
        boolean b7 = Objects.equals(intValue, integerValue);
        boolean b8 = intValue == integerValue;
    }
}

这里大伙可以想想b1~b8各自是什么值

b1~b8结果,和相关说明(点击展开)
b1: true 代码等同于 integerValue.intValue() == intValue
b2: true 代码等同于 intValue == integerValue.intValue() 
b3: true 代码等同于 Objects.equals(integerValue, Integer.valueOf(intValue))
b4: true 代码等同于 Objects.equals(Integer.valueOf(intValue), integerValue)
b5: true 代码等同于 integerValue.equals(Integer.valueOf(intValue));
b6: false 代码等同于  Objects.equals(integerValue, Integer.valueOf(intValue))
b7: false 代码等同于 Objects.equals(Integer.valueOf(intValue), integerValue)
b8: java.lang.NullPointerException 异常 代码等同于 intValue == integerValue.intValue() 

3. 示例代码字节码 (bytecode)

BoxingTest.class文件所在路径,使用指令 javap -p -v -c BoxingTest 输出字节码(bytecode)

“2. 自动装箱/拆箱代码示例 (Boxing/Unboxing Conversion)”中的示例代码类BoxingTest编译生成的class文件(BoxingTest.class)对应的字节码如下

Classfile /Users/ashiamd/Downloads/tmp/javademo/BoxingTest.class
  Last modified 2023年10月31日; size 716 bytes
  MD5 checksum 0c3f3c796bd12ae60e3b5e8a9ac83efb
  Compiled from "BoxingTest.java"
public class BoxingTest
  minor version: 0
  major version: 55
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #6                          // BoxingTest
  super_class: #7                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #7.#19         // java/lang/Object."<init>":()V
   #2 = Methodref          #15.#20        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #3 = Methodref          #15.#21        // java/lang/Integer.intValue:()I
   #4 = Methodref          #22.#23        // java/util/Objects.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z
   #5 = Methodref          #15.#24        // java/lang/Integer.equals:(Ljava/lang/Object;)Z
   #6 = Class              #25            // BoxingTest
   #7 = Class              #26            // java/lang/Object
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               main
  #13 = Utf8               ([Ljava/lang/String;)V
  #14 = Utf8               StackMapTable
  #15 = Class              #27            // java/lang/Integer
  #16 = Class              #28            // "[Ljava/lang/String;"
  #17 = Utf8               SourceFile
  #18 = Utf8               BoxingTest.java
  #19 = NameAndType        #8:#9          // "<init>":()V
  #20 = NameAndType        #29:#30        // valueOf:(I)Ljava/lang/Integer;
  #21 = NameAndType        #31:#32        // intValue:()I
  #22 = Class              #33            // java/util/Objects
  #23 = NameAndType        #34:#35        // equals:(Ljava/lang/Object;Ljava/lang/Object;)Z
  #24 = NameAndType        #34:#36        // equals:(Ljava/lang/Object;)Z
  #25 = Utf8               BoxingTest
  #26 = Utf8               java/lang/Object
  #27 = Utf8               java/lang/Integer
  #28 = Utf8               [Ljava/lang/String;
  #29 = Utf8               valueOf
  #30 = Utf8               (I)Ljava/lang/Integer;
  #31 = Utf8               intValue
  #32 = Utf8               ()I
  #33 = Utf8               java/util/Objects
  #34 = Utf8               equals
  #35 = Utf8               (Ljava/lang/Object;Ljava/lang/Object;)Z
  #36 = Utf8               (Ljava/lang/Object;)Z
{
  public BoxingTest();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=11, args_size=1
         0: sipush        128
         3: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         6: astore_1
         7: sipush        128
        10: istore_2
        11: aload_1
        12: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
        15: iload_2
        16: if_icmpne     23
        19: iconst_1
        20: goto          24
        23: iconst_0
        24: istore_3
        25: iload_2
        26: aload_1
        27: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
        30: if_icmpne     37
        33: iconst_1
        34: goto          38
        37: iconst_0
        38: istore        4
        40: aload_1
        41: iload_2
        42: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        45: invokestatic  #4                  // Method java/util/Objects.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z
        48: istore        5
        50: iload_2
        51: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        54: aload_1
        55: invokestatic  #4                  // Method java/util/Objects.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z
        58: istore        6
        60: aload_1
        61: iload_2
        62: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        65: invokevirtual #5                  // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
        68: istore        7
        70: aconst_null
        71: astore_1
        72: aload_1
        73: iload_2
        74: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        77: invokestatic  #4                  // Method java/util/Objects.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z
        80: istore        8
        82: iload_2
        83: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        86: aload_1
        87: invokestatic  #4                  // Method java/util/Objects.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z
        90: istore        9
        92: iload_2
        93: aload_1
        94: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
        97: if_icmpne     104
       100: iconst_1
       101: goto          105
       104: iconst_0
       105: istore        10
       107: return
      LineNumberTable:
        line 6: 0
        line 7: 7
        line 8: 11
        line 9: 25
        line 10: 40
        line 11: 50
        line 12: 60
        line 13: 70
        line 14: 72
        line 15: 82
        line 16: 92
        line 17: 107
      StackMapTable: number_of_entries = 6
        frame_type = 253 /* append */
          offset_delta = 23
          locals = [ class java/lang/Integer, int ]
        frame_type = 64 /* same_locals_1_stack_item */
          stack = [ int ]
        frame_type = 252 /* append */
          offset_delta = 12
          locals = [ int ]
        frame_type = 64 /* same_locals_1_stack_item */
          stack = [ int ]
        frame_type = 255 /* full_frame */
          offset_delta = 65
          locals = [ class "[Ljava/lang/String;", class java/lang/Integer, int, int, int, int, int, int, int, int ]
          stack = []
        frame_type = 64 /* same_locals_1_stack_item */
          stack = [ int ]
}
SourceFile: "BoxingTest.java"

这里先不展开聊字节码指令集(Instruction Set)中每个指令的含义。这里先直接将部分关键指令注释到示例代码中,如下:

import java.util.Objects;

public class BoxingTest {

    public static void main(String[] args) {
        // Code: 3: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        Integer integerValue = Integer.valueOf(128);
        int intValue = 128;
        // Code: 12: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
        boolean b1 = integerValue == intValue;
        // Code: 27: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
        boolean b2 = intValue == integerValue;
        // Code: 42: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        boolean b3 = Objects.equals(integerValue, intValue);
        // Code: 51: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        boolean b4 = Objects.equals(intValue, integerValue);
        // Code: 62: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        boolean b5 = integerValue.equals(intValue);
        integerValue = null;
        // Code: 74: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        boolean b6 = Objects.equals(integerValue, intValue);
        // Code: 83: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        boolean b7 = Objects.equals(intValue, integerValue);
        // Code: 94: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
        boolean b8 = intValue == integerValue;
    }
}

根据上面的注释,可简单给出几个结论:

  1. Integerint变量通过==运算符比较时,会调用Integer变量的实例方法intValue()来获取int值
  2. Integerint变量通过Objects.equals()静态方法或Integer的实例方法equals()比较时,会调用Integer静态方法valueOf()int变量值获取Integer变量

根据以上结论,不然得出boolean b8 = intValue == integerValue; 由于变量integerValue为null,调用Integer实例方法intValue()导致NPE异常。

4. java SE对装箱拆箱的描述 (Boxing/Unboxing Conversion)

5.1.7. Boxing Conversion

5.1.8. Unboxing Conversion

结合前面我们的demo,简单结论:

  • Boxing,调用包装类的valueOf方法,将基础类型转成包装类
  • Unboxing,调用包装类的xxxValue()方法,将包装类转成基础类型

比如:

  • Integer => int, 代码: Integer integerValue = 1; int intValue = integerValue.intValue();
  • int => Integer, 代码: int intValue = 1; Integer integerValue = Integer.valueOf(intValue);

5. 方法栈帧(Frames)

2.6. Frames
java虚拟机栈空间 <= 下面的图片来自该文章

Stack area of jvm

简言之,每当一个方法被某线程调用时(a method is invoked), JVM创建为该线程创建一个栈帧(Frame), 并且在方法执行结束(return)或抛出异常(throw)后, 视为当前方法执行结束, 销毁栈帧。

栈帧(Frame)主要由3部分数据组成:

  1. Local Variables: 本地变量表(局部变量表),数组结构
  2. Operand Stacks: 操作数栈,栈结构,实际的方法逻辑执行主要发生在该区域
  3. Run-Time Constant Pool: 运行时常量池。有时第三部分也被称为Frame Data,区别不大,也是包含常量信息(方法跳转地址, 异常处理信息等)

下面的demo代码中,我们主要关注Code部分,Code中的指令即Instruction/Opcode。JVM虚拟机一条指令(instruction)通常由一个操作码(opcode)和N个操作数(operand)组成,其中有许多指令没有操作数,只由一个操作码组成。

Code中的指令执行,与我们后续要关注的Local VariablesOperand Stacks内的数据变化息息相关。

5.1 Local VariablesOperand Stacks-Demo代码1

Demo代码1:

public class ByteCodeDemo01 {
    public static int add(int a, int b) {
        return a + b;
    }
}

通过javap -p -v -c ByteCodeDemo01获取对应的字节码(bytecode):

Classfile /Users/ashiamd/Downloads/tmp/javademo/ByteCodeDemo01.class
  Last modified 2023年11月9日; size 258 bytes
  MD5 checksum 69fef7ff385bf61e4b2c7e0023dab176
  Compiled from "ByteCodeDemo01.java"
public class ByteCodeDemo01
  minor version: 0
  major version: 55
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #2                          // ByteCodeDemo01
  super_class: #3                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #3.#12         // java/lang/Object."<init>":()V
   #2 = Class              #13            // ByteCodeDemo01
   #3 = Class              #14            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               add
   #9 = Utf8               (II)I
  #10 = Utf8               SourceFile
  #11 = Utf8               ByteCodeDemo01.java
  #12 = NameAndType        #4:#5          // "<init>":()V
  #13 = Utf8               ByteCodeDemo01
  #14 = Utf8               java/lang/Object
{
  public ByteCodeDemo01();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static int add(int, int);
    descriptor: (II)I
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=2
         0: iload_0
         1: iload_1
         2: iadd
         3: ireturn
      LineNumberTable:
        line 3: 0
}
SourceFile: "ByteCodeDemo01.java"

这里有两个方法,我们逐个分析。

5.1.1 public ByteCodeDemo01();

2.11. Instruction Set Summary

 public ByteCodeDemo01();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

我们没有显式定义构造方法,所以JVM给我们生成了默认的无参构造方法,对应字节码中的public ByteCodeDemo01();

  • descriptor: ()V: 方法描述符,由于没有形参要求,所以参数列表为空(); 返回值V对应java代码的void, 表示无返回值
  • flags: (0x0001) ACC_PUBLIC: 访问描述符,这里对应public
  • Code: 即方法内实际执行的逻辑对应的指令, stack=1说明Operand Stack大小为1, locals=1表示Local Variable大小为1,args_size=1表示当前方法形参数量为1
  • LineNumberTable: 主要用于debug,记录Code中指令和java源代码中的行号对应关系,平时不需要特地关注,下面不再介绍

这里以及后续,我们主要关注descriptor, flags, Code三部分。

  1. args_size=1: 看似构造方法没有形参,实际第一个参数是当前实例的this引用
  2. locals=1: 后续方法执行需要用到this引用,所以本地/局部变量表在下标0的位置存储this引用,引用类型占用1个slot(32bit)
  3. stack=1: 这里执行逻辑时,只涉及一次指令父类构造方法调用,只需将局部变量表的this引用加载到栈顶并消耗,this只占用1个slot(32bit)

locals对应Local Variable的大小,可以理解为数组结构,并且在编译时就确定了大小;
stack对应Operand Stack的大小,可以理解为栈结构,同样编译期确定了大小。

locals和stack的变化可以如下表示(这里用[]表示Local Variable, 用{}表示Operand Stack):

// [this] | {} 
0: aload_0 // 将Local Variable下标0的this引用加载到栈顶
// [this] | {this}
1: invokespecial #1                  // Method java/lang/Object."<init>":()V // 调用非静态方法,构造方法,消耗this引用
// [this] | {}
4: return // return,方法返回,清空`Local Variable`和`Operand Stack`
// [] | {}

invokespecial

  • aload_0: a表示引用类型,load即表示将Local Variable的数据加载到Operand Stack栈顶,0表示从Local Variable下标0的位置加载
  • invokespecial: 这里用于超类(父类)构造方法调用 (JVM文档描述: Invoke instance method; direct invocation of instance initialization methods and methods of the current class and its supertypes)
  • return: 很好理解,表示方法返回,因为是无返回值的方法,所以直接return,无其他前缀,比如ireturn表示返回栈顶的int

5.1.2 public static int add(int, int);

public static int add(int, int);
    descriptor: (II)I
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=2
         0: iload_0
         1: iload_1
         2: iadd
         3: ireturn
      LineNumberTable:
        line 3: 0
  • descriptor: (II)I: 括号里的表示形参,这里两个形参都是int,int用I表示,返回值在右括号右边,也是I,表示返回值为int类型
  • flags: (0x0009) ACC_PUBLIC, ACC_STATIC: ACC_PUBLIC标识public方法,ACC_STATIC标识static静态方法
  • stack=2, locals=2, args_size=2: 这里args_size=2,对应形参2个int类型(静态方法没有this引用),很好理解,而stack=2locals=2分别表示Operand StackLocal Variable的大小,下面再分析

locals和stack的变化可以如下表示(这里用[]表示Local Variable, 用{}表示Operand Stack):

// [int, int] | {}, 注意这里是静态方法,所以Local Variable下标0的位置不是this引用,而2个int分别表示add方法的2个int形参
0: iload_0 // 加载Local Variable下标0的int值到Operand Stack栈顶
// [int, int] | {int}
1: iload_1 // 加载Local Variable下标1的int值到Operand Stack栈顶
// [int, int] | {int, int}
2: iadd // 消耗栈顶2个int值,进行相加操作,并且将int结果放回栈顶
// [int, int] | {int}
3: ireturn
// [] | {}

上面Local VariableOperand Stack变化可以看出来locals=2,而栈需要2个slot,stack=2

  • iload_<n>: 这里i表示int值,load表示从Local Variable加载数据到Operand Stack栈顶,<n>表示从Local Variable下标n的位置加载数据
  • iadd: 消耗Operand Stack栈顶2个数据,然后进行add相加操作,并且将int返回值放会Operand Stack栈顶
  • ireturn: 返回Operand Stack栈顶的int值

5.2 Local VariablesOperand Stacks-Demo代码2

Demo代码2:

public class ByteCodeDemo02 {

    private int a;
    private int b;

    public ByteCodeDemo02(int a, int b) {
        this.a = a;
        this.b = b;
    }

    public static int sub(int a, int b) {
        return a - b;
    }
}

通过javap -p -v -c ByteCodeDemo02获取对应的字节码(bytecode):

Classfile /Users/ashiamd/Downloads/tmp/javademo/ByteCodeDemo02.class
  Last modified 2023年11月9日; size 336 bytes
  MD5 checksum fffd2d8636fdfb521e5fd710125ec062
  Compiled from "ByteCodeDemo02.java"
public class ByteCodeDemo02
  minor version: 0
  major version: 55
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #4                          // ByteCodeDemo02
  super_class: #5                         // java/lang/Object
  interfaces: 0, fields: 2, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #5.#17         // java/lang/Object."<init>":()V
   #2 = Fieldref           #4.#18         // ByteCodeDemo02.a:I
   #3 = Fieldref           #4.#19         // ByteCodeDemo02.b:I
   #4 = Class              #20            // ByteCodeDemo02
   #5 = Class              #21            // java/lang/Object
   #6 = Utf8               a
   #7 = Utf8               I
   #8 = Utf8               b
   #9 = Utf8               <init>
  #10 = Utf8               (II)V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               sub
  #14 = Utf8               (II)I
  #15 = Utf8               SourceFile
  #16 = Utf8               ByteCodeDemo02.java
  #17 = NameAndType        #9:#22         // "<init>":()V
  #18 = NameAndType        #6:#7          // a:I
  #19 = NameAndType        #8:#7          // b:I
  #20 = Utf8               ByteCodeDemo02
  #21 = Utf8               java/lang/Object
  #22 = Utf8               ()V
{
  private int a;
    descriptor: I
    flags: (0x0002) ACC_PRIVATE

  private int b;
    descriptor: I
    flags: (0x0002) ACC_PRIVATE

  public ByteCodeDemo02(int, int);
    descriptor: (II)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iload_1
         6: putfield      #2                  // Field a:I
         9: aload_0
        10: iload_2
        11: putfield      #3                  // Field b:I
        14: return
      LineNumberTable:
        line 6: 0
        line 7: 4
        line 8: 9
        line 9: 14

  public static int sub(int, int);
    descriptor: (II)I
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=2
         0: iload_0
         1: iload_1
         2: isub
         3: ireturn
      LineNumberTable:
        line 12: 0
}
SourceFile: "ByteCodeDemo02.java"

5.2.1 public ByteCodeDemo02(int, int);

public ByteCodeDemo02(int, int);
    descriptor: (II)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iload_1
         6: putfield      #2                  // Field a:I
         9: aload_0
        10: iload_2
        11: putfield      #3                  // Field b:I
        14: return
      LineNumberTable:
        line 6: 0
        line 7: 4
        line 8: 9
        line 9: 14
  • descriptor: (II)V: 这里是构造方法,所以返回值V表示void,而(II)表示方法形参2个int值
  • flags: (0x0001) ACC_PUBLIC: ACC_PUBLIC 表示public方法
  • stack=2, locals=3, args_size=3: args_size=3, 因为实例方法含有this引用,其次形参数有2个int类型,加和为3; 方法里没有局部变量,根据形参和this可提前得出locals=3

locals和stack的变化可以如下表示(这里用[]表示Local Variable, 用{}表示Operand Stack):

// [this, int, int] | {}
0: aload_0 // Local Variable 下标0的引用类型数据加载到 Operand Stack栈顶
// [this, int, int] | {this}
1: invokespecial #1                  // Method java/lang/Object."<init>":()V // 调用超类构造方法,消耗栈顶引用类型数据
// [this, int, int] | {}
4: aload_0 // 加载Local Variable下标0位置的this引用到栈顶
// [this, int, int] | {this}
5: iload_1 // 加载Local Variable下标1位置的int值到栈顶
// [this, int, int] | {this, int}
6: putfield      #2                  // Field a:I // 消耗栈顶的数据和后续的this指针,完成实例成员变量a赋值操作
// [this, int, int] | {}
9: aload_0 // 同理加载this引用到栈顶
// [this, int, int] | {this}
10: iload_2 // 加载Local Variable下标2位置的int值到栈顶
// [this, int, int] | {this, int}
11: putfield      #3                  // Field b:I // 消耗栈顶的数据和后续的this指针,完成实例成员变量b赋值操作
// [this, int, int] | {}
14: return // 方法无返回值,方法返回
// [] | {}

可以看出来,栈里面最多同时只有2个slot被使用,所以stack=2

  • aload_<n>: a表示引用类型数据,load表示将Local Variable的数据加载到Operand Stack,<n>表示从Local Variable下标n的位置加载数据
  • iload_<n>: i表示int值,load表示从Local Variable加载数据到Operand Stack栈顶,<n>表示从Local Variable下标n的位置加载数据
  • iadd: 消耗Operand Stack栈顶2个数据,然后进行add相加操作,并且将int返回值放会Operand Stack栈顶
  • putfield: 消耗栈顶数据和this引用,对指定的实例字段赋值。上面字节码中操作数(operand)#2#3分别对应常量池(Constant Pool)符号引用ByteCodeDemo02.a:IByteCodeDemo02.b:I,即成员变量a和成员变量b
  • return: 无返回值的方法返回

5.2.2 public static int sub(int, int);

public static int sub(int, int);
    descriptor: (II)I
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=2
         0: iload_0
         1: iload_1
         2: isub
         3: ireturn
      LineNumberTable:
        line 12: 0
  • descriptor: (II)I: (II)表示形参为2个int类型值,右括号右边的I表示返回值为int类型
  • flags: (0x0009) ACC_PUBLIC, ACC_STATIC: ACC_PUBLIC标识public方法,ACC_STATIC标识static静态方法
  • stack=2, locals=2, args_size=2: args_size=2因为形参就2个int值;locals=2因为没有局部变量,且静态方法无this引用,所以Local Variable大小对应形参大小2个slot存储2个int数据

locals和stack的变化可以如下表示(这里用[]表示Local Variable, 用{}表示Operand Stack):

// [int, int] | {}
0: iload_0 // 加载Local Variable下标0位置的int值到Operand Stack栈顶
// [int, int] | {int}
1: iload_1 // 加载Local Variable下标1位置的int值到Operand Stack栈顶
// [int, int] | {int, int}
2: isub // 栈顶2个int值相减,并且返回int结果到Operand Stack顶部
// [int, int] | {int}
3: ireturn // 返回栈顶部int值
// [] | {}

逻辑执行过程中,栈顶最多同时存在2个slot大小的数据,所以stack=2

  • iload_<n>: i表示int值,load表示从Local Variable加载数据到Operand Stack栈顶,<n>表示从Local Variable下标n的位置加载数据
  • isub: 消耗Operand Stack栈顶2个数据,然后进行sub相减操作,并且将int返回值放会Operand Stack栈顶
  • ireturn: 消耗栈顶int数据并返回

5.3 Opcode查阅方法

可查阅JVM文档,对每个Opcode有详细的说明。

Chapter 6. The Java Virtual Machine Instruction Set