Java:在JVM层面的讨论问题“++i与i++的区别”(字节码指令、操作数栈、局部变量表)

引言:本文通过一个问题来引出字节码指令、运行时数据区栈帧的简单结构,从JVM的角度来回答这个问题,笔者只是做一个学习总结,如有不解之处可以评论区提问,若有错漏之处欢迎批评指正!

1.先从代码的角度分析

java 复制代码
    @Test
    public void test1(){
        int i = 10;
        i++;
        //++i;
        System.out.println(i);// 结果都是11
    }

毫无疑问这里i的值都是11

java 复制代码
    @Test
    public void test2(){
        int i = 10;
        i = i++;
        //i = ++i;
        System.out.println(i);// 结果分别是i++:10,++i:11
    }

第二段代码就有点疑问了,初学Java时,有一句话是这么总结的,++在后则先复制再加+1,++在前则反之。 按照这个总结,那先给i赋值后再+1,结果难道不应该是11?其实不然,还有一个规则就是i++并没有使用过,所以导致最终并不能保存下来(暂时只能这么理解i++)

java 复制代码
    @Test
    public void test3(){
        int k = 10;
        k = k + (k++) + (++k);
        System.out.println(k);//32
    }

第三段代码比较好分析,k=10+10+12,即结果为32 目前的问题是,我们用的是一个总结即"++在前,先+1再运算;++在后,先运算再+1"来进行分析代码,然后对于同一个变量为何自加后没有改变其自身的值,这是为什么呢?

2.字节码文件与字节码指令

众所周知,我们编写好的java文件,经过编译器的编译生成class文件即字节码文件,它是一种二进制文件,其中包含字节码指令,这些指令按照一个字节长度的、代表着某种特定操作含义的操作码(opcode)以及跟随其后的零至多个代表此操作所需参数的操作数(operand)所构成。

JVM通过识别并执行这些指令来实现我们的功能。我们可以通过工具进行反编译来将二进制的文件变成可视化的操作码和操作数组成的一行行指令,这样我们就可以阅读了。

本文使用的是IDEA插件jclasslib。 通过编译与反编译可以得到以下指令:

test1方法:

test2方法: i = i++的情况:

i = ++i的情况:

非常明显能看到对应的代码中有两行指令顺序不一致!

test3方法:

这样我们就得到了每个方法的字节码指令,但是具体的指令代表什么意思呢?又是怎么执行呢?本文尽量简化,只用到局部变量表和操作数栈的结构来解释。

3.运行

这里尽量简化了结构:

test1方法运行

bipush :将单字节的常量值x(-128~127)推送至栈顶(入栈)

istore_x:将栈顶int型数值存入本地变量表索引x位置(出栈)

iinc x by y:将局部变量表x位置的值增加指定值y

test2方法运行

关注i = i++;三条指令,③④⑤此类编号表示上面图示中方法字节码指令最左侧的行号 ③iload_x:将局部变量表位置x的值加载到栈顶(入栈)

④:将局部变量表位置1的值增加1

⑤:将栈顶的int值保存到局部变量表位置1的位置(出栈)(覆盖原本的值)

如果这里的代码是i = ++i,那么①和②的顺序调换一下即可。 所以由此可以得到,++ii++在JVM层面的区别! ++操作的字节码指令为iinc x by y JVM在执行字节码指令时,如果是变量名在前,则先将对应的局部变量的值入栈,再进行innc操作;反之则先进行innc操作,再入栈!

test3方法运行

关注代码k = k + (k++) + (++k);,③④⑤此类编号表示上面图示中方法字节码指令最左侧的行号 ③:将局部变量表位置1的值压入栈顶

④:将局部变量表位置1的值压入栈顶

⑤:将局部变量表位置1的值增加1

iadd:将栈顶两个int数值相加并压入栈顶

⑦:将局部变量表位置1的值增加1

⑧:将局部变量表位置1的值压入栈顶

⑨:将栈顶两个int数值相加并压入栈顶

⑩:将栈顶的int型数值出栈保存到局部变量表位置1中

4.总结

JVM层面讨论的就是字节码指令的执行。不同的代码编译后产生的字节码指令以及不同的顺序,都会影响最后的执行结果。这中还包含了运行时数据区:虚拟机栈中栈帧的局部变量表和操作数栈的相关知识。学习Java和有必要去了解底层,知其然而不知其所以然是不可取的。

相关推荐
星星不打輰几秒前
Spring基于注解进行开发
java·spring
陈大爷(有低保)2 分钟前
Spring中都用到了哪些设计模式
java·后端·spring
骑牛小道士9 分钟前
JAVA- 锁机制介绍 进程锁
java·开发语言
高林雨露12 分钟前
Java对比学习Kotlin的详细指南(一)
java·学习·kotlin
雷渊1 小时前
深入分析mybatis中#{}和${}的区别
java·后端·面试
亦是远方1 小时前
2025华为软件精英挑战赛2600w思路分享
android·java·华为
花月C1 小时前
Spring IOC:容器管理与依赖注入秘籍
java·开发语言·rpc
ylfhpy1 小时前
Java面试黄金宝典22
java·开发语言·算法·面试·职场和发展
风象南2 小时前
Spring Boot 实现文件秒传功能
java·spring boot·后端
橘猫云计算机设计2 小时前
基于django优秀少儿图书推荐网(源码+lw+部署文档+讲解),源码可白嫖!
java·spring boot·后端·python·小程序·django·毕业设计