从面试题i++和++i来看java字节码
我们先来看一道经典面试题
1 | public class Demo05 { |
使用1
2
Classfile /E:/workplace/abc/jvmStudy/out/production/jvmStudy/demo1/Demo05.class
Last modified 2020-2-23; size 567 bytes
MD5 checksum e378885aa1eef9f5fd48fda536b4467b
Compiled from “Demo05.java”
public class demo1.Demo05
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#22 // java/lang/Object.”
#2 = Fieldref #23.#24 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Methodref #25.#26 // java/io/PrintStream.println:(I)V
#4 = Class #27 // demo1/Demo05
#5 = Class #28 // java/lang/Object
#6 = Utf8
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 LocalVariableTable
#11 = Utf8 this
#12 = Utf8 Ldemo1/Demo05;
#13 = Utf8 main
#14 = Utf8 ([Ljava/lang/String;)V
#15 = Utf8 args
#16 = Utf8 [Ljava/lang/String;
#17 = Utf8 a
#18 = Utf8 I
#19 = Utf8 b
#20 = Utf8 SourceFile
#21 = Utf8 Demo05.java
#22 = NameAndType #6:#7 // “
#23 = Class #29 // java/lang/System
#24 = NameAndType #30:#31 // out:Ljava/io/PrintStream;
#25 = Class #32 // java/io/PrintStream
#26 = NameAndType #33:#34 // println:(I)V
#27 = Utf8 demo1/Demo05
#28 = Utf8 java/lang/Object
#29 = Utf8 java/lang/System
#30 = Utf8 out
#31 = Utf8 Ljava/io/PrintStream;
#32 = Utf8 java/io/PrintStream
#33 = Utf8 println
#34 = Utf8 (I)V
{
public demo1.Demo05();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object.”
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ldemo1/Demo05;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: bipush 10
2: istore_1
3: iload_1
4: iinc 1, 1
7: iinc 1, 1
10: iload_1
11: iadd
12: iload_1
13: iinc 1, -1
16: iadd
17: istore_2
18: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
21: iload_1
22: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
25: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
28: iload_2
29: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
32: return
LineNumberTable:
line 5: 0
line 6: 3
line 7: 18
line 8: 25
line 9: 32
LocalVariableTable:
Start Length Slot Name Signature
0 33 0 args [Ljava/lang/String;
3 30 1 a I
18 15 2 b I
}
SourceFile: “Demo05.java”1
2
3
4
现在我们逐行来分析反编译后的字节码
Classfile /E:/workplace/jvmStudy/out/production/jvmStudy/demo1/Demo05.class //字节码原文件所在位置
Last modified 2020-2-23; size 567 bytes //最后修改时间,以及文件大小
MD5 checksum e378885aa1eef9f5fd48fda536b4467b //MD5签名
Compiled from “Demo05.java” //字节码文件是由哪个文件编译得来的
public class demo1.Demo05 //类名
minor version: 0 // 小版本号
major version: 52 //大版本号 52 对应java8
flags: ACC_PUBLIC, ACC_SUPER //ACC_PUBLIC表示类的访问修饰符为Public ACC_SUPER,具体见表11
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
## 表1 类访问标记(access flags)
| Flag Name | Value | Interpretation |
| -------------- | ----- | ------------------- |
| ACC_PUBLIC | 1 | 标识是否是 public |
| ACC_FINAL | 10 | 标识是否是 final |
| ACC_SUPER | 20 | 已经不用了 |
| ACC_INTERFACE | 200 | 标识是类还是接口 |
| ACC_ABSTRACT | 400 | 标识是否是 abstract |
| ACC_SYNTHETIC | 1000 | 编译器自动生成,不是用户源代码编译生成 |
| ACC_ANNOTATION | 2000 | 标识是否是注解类 |
| ACC_ENUM | 4000 | 标识是否是枚举类 |
---
Constant pool:
#1 = Methodref #5.#22 // java/lang/Object.”
#2 = Fieldref #23.#24 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Methodref #25.#26 // java/io/PrintStream.println:(I)V
#4 = Class #27 // demo1/Demo05
#5 = Class #28 // java/lang/Object
#6 = Utf8
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 LocalVariableTable
#11 = Utf8 this
#12 = Utf8 Ldemo1/Demo05;
#13 = Utf8 main
#14 = Utf8 ([Ljava/lang/String;)V
#15 = Utf8 args
#16 = Utf8 [Ljava/lang/String;
#17 = Utf8 a
#18 = Utf8 I
#19 = Utf8 b
#20 = Utf8 SourceFile
#21 = Utf8 Demo05.java
#22 = NameAndType #6:#7 // “
#23 = Class #29 // java/lang/System
#24 = NameAndType #30:#31 // out:Ljava/io/PrintStream;
#25 = Class #32 // java/io/PrintStream
#26 = NameAndType #33:#34 // println:(I)V
#27 = Utf8 demo1/Demo05
#28 = Utf8 java/lang/Object
#29 = Utf8 java/lang/System
#30 = Utf8 out
#31 = Utf8 Ljava/io/PrintStream;
#32 = Utf8 java/io/PrintStream
#33 = Utf8 println
#34 = Utf8 (I)V1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
上面类的内容为该类的常量池
1. 第一列 #+数字代表常量池的编号
2. 第二列 Utf8/NameAndType........ 代表常量的类型
3. 第三列表示常量的具体数值,如果是#+数字,表示引用其他常量数据
---
然后我们来看下构造方法,虽然我们没有显示的写一个构造方法,但是在编译的时候,会默认创建一个无参的构造方法,
从下面的字节码可以看出:
```java
public demo1.Demo05(); //Demo05方法
descriptor: ()V //参数为空,返回值为void ---V代表Void 也就是无返回值
flags: ACC_PUBLIC //表示方法的访问标记,是 public、private 还是 protected,是否是 static,是否是 final 等 具体见表1。
Code: // 方法体
stack=1, locals=1, args_size=1 // 栈帧深度为1 局部变量表中变量数为1 该方法的形参个数。如果是实例方法,第一个形参是this引用。
0: aload_0 //aload 表示加载本地变量表的数据,这里表示加载本地变量表中第一条数据,为this
1: invokespecial #1 // Method java/lang/Object."<init>":()V
// 调用Object.init方法
4: return //返回
LineNumberTable: //行号表,表示Code的字节码对应代码的行号
line 3: 0
/*
* 局部变量表
* start表示从Code的字节码第几行开始创建该局部变量
* Length表示变量的生命周期 code字节码中的(0-4),所以为5
* Slot 表示变量的序号
* Name 表示变量名
* Name 表示变量引用
*/
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ldemo1/Demo05;
备注:每个方法会创建一个栈帧.类似于一个先进后(FILO)出的队列
表2 方法访问标记(access flags)
方法的访问标记比类和字段的访问标记类型更丰富,有 12 种之多,如小表所示:
方法访问标记 | 值 | 描述 |
---|---|---|
ACC_PUBLIC | 0x0001 | 声明为 public |
ACC_PRIVATE | 0x0002 | 声明为 private |
ACC_PROTECTED | 0x0004 | 声明为 protected |
ACC_STATIC | 0x0008 | 声明为 static |
ACC_FINAL | 0x0010 | 声明为 final |
ACC_SYNCHRONIZED | 0x0020 | 声明为 synchronized |
ACC_BRIDGE | 0x0040 | bridge 方法, 由编译器生成 |
ACC_VARARGS | 0x0080 | 方法包含可变长度参数,比如 String… args |
ACC_NATIVE | 0x0100 | 声明为 native |
ACC_ABSTRACT | 0x0400 | 声明为 abstract |
ACC_STRICT | 0x0800 | 声明为 strictfp,表示使用 IEEE-754 规范的精确浮点数,极少使用 |
ACC_SYNTHETIC | 0x1000 | 表示这个方法是由编译器自动生成,而不是用户代码编译产生 |
重点来了
下面我们来看Main方法的字节码:
1 | public static void main(java.lang.String[]); //main方法 |
从上面的字节码可以看出,i++ 和++i 的操作与其他操作不同
- 直接在局部变量表中操作.
- i++ 是先从局部变量表中加载到栈中,然后再局部变量表中运算
- ++i 是先在局部变量表中运算,然后再加载到栈中
这就是为什么i++ 先返回,后相加;++i是先相加后返回.也就是上面面试题打印结果为11,34的原因.
以上仅为个人观点,如有错误,欢迎指正.谢谢!