字节码详解x=x++

遇到这样一个八股之前被硬控很久,最近在深入学习字节码指令后豁然开朗,因此想从字节码+jvm底层图解的角度来解释这个问题,帮助日后同样被这个问题困惑而又看不懂复杂字节码的人搞明白这个问题

原八股题如下

 public static void main(String[] args) {
        int x=0;
        for (int i = 0; i < 10; i++) {
            x=x++;
        }
        System.out.println(x);//最终打印x为0
    }

问:为什么x的值未发生变化?

下面我将从四个例子来循序渐进讲明白这个问题

我们利用这个简单的demo来分析(初始化x,y之后的四行语句分别对应下文的四条分析)

 public static void main(String[] args) {
        int x=0;
        int y=0;
     	x=x+1;	//情况1
        x++;	//情况2
     	y=x++;	//情况3
     	x=x++;	//情况4
    }

先来从内存的角度看看初始化x和y后,赋值语句之前发生了什么:

jvm加载一个class字节码文件后会将字节码指令加载到方法区中,同时根据字节码的信息在当前线程栈帧中创建对应大小的局部变量表操作数栈,这里我们要对这两个结构比较敏感,因为下文就是围绕这二者展开

  • 局部变量表:存储变量的值,会为我们定义的变量开辟对应空间,运行过程中该变量的值都是记录在这里
  • 操作数栈:临时存储指令的输入参数和计算结果。在对变量执行一些操作时,比如int的加减乘除等,都需要将数据加载到操作数栈中进行操作 alt

比如这两条字节码指令执行的操作--int x=0;

0 iconst_0	//对应图中的 1.将常数0加载到操作数栈
1 istore_1	//对应图中   2.将栈顶的值(出栈)赋值给槽1(也就是变量x)

alt

大致了解这两个结构后再来跟进一步看其他赋值语句

x=x+1

先来看看常规的x+1赋值给x在底层是如何操作的

反编译后的字节码

0 iconst_0	
1 istore_1
2 iconst_0
3 istore_2	//以上4条是将0,0分别赋值给局部变量表的x,y实现初始化,对应x=0;y=0;
4 iload_1	//对应图中的 1.取出x的值到操作数栈
5 iconst_1	//对应图中的 2.加载常数1到操作数栈
6 iadd		//对应图中 3.相加后的值存到栈中
7 istore_1	//对应图中 4.将栈顶的和赋值给局部变量表的x
8 return

alt

可以看到,x=x+1是先将x的值从局部变量表中取出压入操作数栈,然后在将常数1压入操作数栈,然后执行iadd将栈顶的两个元素相加(这里iadd的操作其实是先将栈顶的两个元素弹出相加,然后再将和压入栈中),然后赋值给局部变量表的x

x++

0 iconst_0
1 istore_1
2 iconst_0
3 istore_2		//此前操作是给局部变量表中的a,b
4 iinc 1 by 1	//这条innc会在局部变量表中将a的值直接+1
7 return

x++这条语句编译后对应一句字节码,即iinc,这条指令的作用是将局部变量表中的值直接+1.而不用经过操作数栈

这是jvm做出的一个优化,可以看到相比x=x+1,自增操作不用频繁出入栈,更加高效,而这也正是问题根源

alt

y=x++/y=++x

编译后对应两句字节码iloadiinc

  • 如果是x++,就是先执行 iload将x的取值加载到操作数栈,然后执行iinc将局部变量表中的x自增+1;然后执行y的赋值操作,将操作数栈中的值存到局部变量表y的位置

  • ++x则是先自增x,后取出值到操作数栈然后赋值给y

(这里只演示y=x++,后者可以读者自己编译字节码查看)

0 iconst_0
1 istore_1
2 iconst_0
3 istore_2
4 iload_1   	//下图 1.局部变量表中取出x值到操作数栈
5 iinc 1 by 1	//下图 2.局部变量表x自增1
8 istore_2		//下图 3.将栈顶元素赋值给y
9 return

alt

x=x++

有了上面三个例子的铺垫,再来看x=x++

0 iconst_0
1 istore_1
2 iconst_0
3 istore_2
4 iload_1		//对应图中 1.将x的原值0取出到操作数栈
5 iinc 1 by 1	//对应图中 2.局部变量表中的x自增1后为1
8 istore_1		//对应图中 3.操作数栈中的0赋值给局部变量表的x
9 return

alt

  1. 执行iload将x的原值取出到操作数栈
  2. 对局部变量表中的x自增1
  3. 将操作数栈中的值赋值给局部变量表的x(即将原来的值重新赋值给x)

综上,x的值没有发生变化

从几个例子的字节码我们也可以得知这个结果就是自增指令iinc的特殊性-->局部变量表直接自增闹出的一个乌龙事件

而将原题中的x=x++换为x=++x打印结果就是 10了,可以结合上面的字节码就可以很容易想到原因了

注:这些是笔者查阅资料总结按照自己的思路总结的,不保证完全正确性,如有错误点恳请指出,共同交流,抱拳

#java#
fengdongnan的博客 文章被收录于专栏

记录fengdongnan的知识产出文档,欢迎大家来一起交流学习

全部评论
mark 字节码
点赞 回复 分享
发布于 03-20 20:58 浙江
mark字节码详解
点赞 回复 分享
发布于 03-17 10:14 江苏
赞一个
点赞 回复 分享
发布于 03-16 21:46 浙江
mark 字节码
点赞 回复 分享
发布于 03-16 21:39 广西

相关推荐

点赞 评论 收藏
分享
04-10 11:56
如皋中学 Java
高斯林的信徒:双c9能简历挂的?
点赞 评论 收藏
分享
评论
3
1
分享

创作者周榜

更多
牛客网
牛客企业服务