浅谈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;
}
}
根据上面的注释,可简单给出几个结论:
Integer
和int
变量通过==
运算符比较时,会调用Integer
变量的实例方法intValue()
来获取int值Integer
和int
变量通过Objects.equals()
静态方法或Integer
的实例方法equals()
比较时,会调用Integer
静态方法valueOf()
以int
变量值获取Integer
变量
根据以上结论,不然得出boolean b8 = intValue == integerValue;
由于变量integerValue
为null,调用Integer
实例方法intValue()
导致NPE异常。
4. java SE对装箱拆箱的描述 (Boxing/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虚拟机栈空间 <= 下面的图片来自该文章
简言之,每当一个方法被某线程调用时(a method is invoked), JVM创建为该线程创建一个栈帧(Frame), 并且在方法执行结束(return)或抛出异常(throw)后, 视为当前方法执行结束, 销毁栈帧。
栈帧(Frame)主要由3部分数据组成:
Local Variables
: 本地变量表(局部变量表),数组结构Operand Stacks
: 操作数栈,栈结构,实际的方法逻辑执行主要发生在该区域Run-Time Constant Pool
: 运行时常量池。有时第三部分也被称为Frame Data,区别不大,也是包含常量信息(方法跳转地址, 异常处理信息等)
下面的demo代码中,我们主要关注Code
部分,Code
中的指令即Instruction/Opcode。JVM虚拟机一条指令(instruction)通常由一个操作码(opcode)和N个操作数(operand)组成,其中有许多指令没有操作数,只由一个操作码组成。
Code
中的指令执行,与我们后续要关注的Local Variables
和 Operand Stacks
内的数据变化息息相关。
5.1 Local Variables
和Operand 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();
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
表示当前方法形参数量为1LineNumberTable
: 主要用于debug,记录Code
中指令和java源代码中的行号对应关系,平时不需要特地关注,下面不再介绍
这里以及后续,我们主要关注descriptor
, flags
, Code
三部分。
args_size=1
: 看似构造方法没有形参,实际第一个参数是当前实例的this
引用locals=1
: 后续方法执行需要用到this
引用,所以本地/局部变量表在下标0的位置存储this
引用,引用类型占用1个slot(32bit)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`
// [] | {}
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=2
和locals=2
分别表示Operand Stack
和Local 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 Variable
和Operand 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 Variables
和Operand 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:I
和ByteCodeDemo02.b:I
,即成员变量a和成员变量breturn
: 无返回值的方法返回
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
标签:lang,Set,java,浅谈,int,Utf8,Instruction,intValue,Integer From: https://www.cnblogs.com/Ashiamd/p/17801058.html