本文以 “i = i++ + ++i + i++ + ++i” 为例 分析 其字节码的执行过程。其中主要解决4个问题
1. i++ 和 ++i 有什么区别,在jvm字节码层面又有什么区别
2. “i = i++ + ++i + i++ + ++i” 最后 i 的值是多少
3. “i = i++ + ++i + i++ + ++i” 这串代码的执行顺序(是从右向左顺序执行,还是像这样"i = (i++ + ++i) + (i++ + ++i)"的执行顺序呢)
4. “i = i++ + ++i + i++ + ++i” 这串代码对应的jvm 字节码 解析和对应的执行流程解析
测试代码:
package com.example.all6.iii;
public class ITest {
public static void main(String[] args){
int i = 1;
i = i++ + ++i + i++ + ++i;
System.out.println(i);
}
}
运行 javap -v -p ITest.class 得到的字节码
...... 省略了
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: iconst_1
1: istore_1
2: iload_1
3: iinc 1, 1
6: iinc 1, 1
9: iload_1
10: iadd
11: iload_1
12: iinc 1, 1
15: iadd
16: iinc 1, 1
19: iload_1
20: iadd
21: istore_1
22: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
25: iload_1
26: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
29: return
LineNumberTable:
这也省略.....
}
SourceFile: "ITest.java"
借个图先:
开始 第四个问题(4. “i = i++ + ++i + i++ + ++i” 这串代码对应的jvm 字节码 解析和对应的执行流程解析):
字节码一共 0 - 29 行, 主要分析0-21行内容,分析每一行的含义和对应的内存的值的变化,首先 图说一下 方法执行过程中 jvm操作数栈 和 局部变量表 的初始情况 如下图所示:
此时的 局部变量表里的i 还没有赋值 , 操作数栈里也没有数据 。this 默认占局部变量表 0 的位置。i 占局部变量表1 的位置(下边的图 好像都忘了 标注了 - -!)。只要知道 变量 i 在局部变量表1的位置
0: iconst_1 : 把 数值 “1” 压入操作数栈栈顶 。此时 操作数栈 栈顶的值是 1
1: istore_1 : 操作数栈 栈顶元素出栈 并存入 局部变量表 位置1 。即相当于给i 赋值为1.i = 1。
2: iload_1 : 将局部变量表位置1 的值 压入操作数栈栈顶
3: iinc 1, 1 : “iinc x,n” 意为 局部变量表x位置上自增n。这里指 局部变量表 1 位置上的数值 自增1。即此时 i 的值为2 。 操作数栈没有变化
6: iinc 1, 1 : 同上 ,此时 i 的值 变为3 。 操作数栈 仍没有变化
9: iload_1 : 把局部变量表1位置的数值 压入栈顶,注意,此时的i 因为两次自增已经变成了 3 。
10: iadd : 把操作数栈 栈顶 和 次栈顶两个元素弹出 相加,再压入栈顶。就是 先弹出3,再弹出1 ,把 1 和 3 相加 。1+3=4 ,把结果 4 再次压入操作数栈 栈顶。此时的 局部变量表没有变化。
11: iload_1 : 把局部变量表1位置的数值 压入栈顶。即 把数值3 压入操作数栈到栈顶位置。此时操作数栈 发生了变化 。看图:
12: iinc 1, 1 : 局部变量表 1的位置上的数值加1 。 即 i 的值由3 变成4 。
15: iadd : 将此时的操作数栈的 临近栈顶的两个元素弹出,即3和4 分别弹出,然后相加 得7 。再压入栈顶。
16: iinc 1, 1 : 局部变量表1的位置自加1。即 4+1=5。i的值变为5。
19: iload_1 : 将局部变量表 1 位置的数值压入操作数栈的栈顶位置。注意 此时的 i 的值 已经是 5 了。
20: iadd : 将操作数栈 临近栈顶的两个元素弹出相加,讲结果压入栈顶。即 先弹出5 再弹出7 ,把 5和7 相加 得到12 ,再压入 操作数栈。此时栈顶的值变成了12。
21: istore_1 : 将操作数栈 栈顶的元素弹出,赋值给局部变量表1位置。即 把 操作数栈栈顶 元素12 弹出,赋值给 i 。相当于 最后给 i 赋值 i=12 。
到此为止 0 - 21 行字节码都一一解析完成了 。剩下的
22: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
25: iload_1
26: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
29: return
这些 无非是 调用打印函数 和 方法结束返回。
总结一下
第四个问题(“i = i++ + ++i + i++ + ++i” 这串代码对应的jvm 字节码 解析和对应的执行流程解析) 相信 仔细看完的朋友 一定能理解 这串代码的执行流程了。
第二问题 (“i = i++ + ++i + i++ + ++i” 最后 i 的值是多少) 12 。对是 12 ,不信你debug试试。为啥 看上边。
第一个问题(i++ 和 ++i 有什么区别,在jvm字节码层面又有什么区别)
通俗的来讲 “i++” 是先赋值 , 再相加 。“++i” 是先相加再赋值。
通过字节码来理解 无论是 “i++” 还是“++i” 都会调用 “iinc 1, 1” 这指令 完成自身加1 的操作。关键取决于这个指令后续的指令是什么。
- 如果是“iload_1” 即将局部变量表位置1 的值 压入操作数栈栈顶。因为此时的i的值已经完成自身加1,这是取的i 的值 或者说以后参与运算的i的值 都是i的最新的值。即 是 2。
- 如果后续指令不是“iload_1” 或者 “istore_1” 。就是说虽然局部变量表i的值完成自增。但是 并没有进入操作数栈参与运算。此时操作数栈 栈顶元素 很可能还是 没有自增前的 值。
至于这两种情况,到底谁“i++” ,谁是“++i”呢 。第1种情况 有后续指令的是“++i”,第二种情况则是“i++”。
为什么 ? 两段代码证明
package com.example.all6.iii;
public class ITest {
public static void main(String[] args){
int i = 1;
int j = 1;
//i = i++ + ++i + i++ + ++i;
i = i++;
j = ++j;
System.out.println(i); // 1
System.out.println(j); // 2
}
}
第三个问题(“i = i++ + ++i + i++ + ++i” 这串代码的执行顺序(是从右向左顺序执行,还是像这样"i = (i++ + ++i) + (i++ + ++i)"的执行顺序呢))
观察这串代码 可以发现有 4个 “++” 操作 和 3 个“+” 操作。
先看图:
从图中 可以推断出 ,字节码 6行和9行 组合是一个“++i” , 16行和19行组合 是一个 “++i” , 剩下的 3行 和 12 行 自然 就是 “i++”了。
先按照 代码从右向左执行顺序来 解读一下
根据字节码从上向下的执行顺序 ,代码从右向左的执行顺序。 得到推测到这样的结果:
5 → 7 → 6 → 1 → 4 → 3 → 2 , 什么乱七八糟的 , 整理一下 i = ((5 + 7) + 1 ) + 3 ,再翻译一下
i = ((i++ + ++i)+ i++ ) + ++i 。
大概能推测出 “i++” 指令的优先级 要比 “++i” 高。或者 “i++” 总比 “i++” 先执行。
至于为什么不是 从右向左的执行顺序 或者 (i++ + ++i) + (i++ + ++i) 这样的执行顺序呢?
应该是 指令重排导致的 (猜的 没有验证过)。
好了,以上就是全部内容 。四个问题 都基本 给出了对应的解释。
推荐 一篇不错的文章 https://www.jianshu.com/p/c11f98269b00
最后 如果有错误,欢迎批评指正