入门篇-其之六-附录一-以Java字节码的角度分析i++和++i
前言:众所周知,
i++和++i的区别是:i++先将i的值赋值给变量,再将i的值自增1;而++i则是先将i的值自增1,再将结果赋值给变量。因此,二者最终都给i自增了1,只是方式不同而已。当然,如果在面试过程中面试官问你这个问题,只回答出上述内容,只能说明你对这方面的知识了解的还是太浅显。那么
i++和++i到底有什么不同之处呢?
一、局部变量表与操作数栈简介
《深入理解Java虚拟机》第八章对栈帧结构有如下描述Java虚拟机以方法作为最基本的执行单元,“栈帧”(Stack Frame)则是用于支持虚拟机进行方法调用和方法执行背后的数据结构,它也是虚拟机运行时数据区中的虚拟机栈的栈元素。
在一个活动线程中,可能会执行多个方法,因此会存在多个栈帧,和“栈”(先进后出)一样,处于栈顶的栈帧才是真正运行的,处于栈顶的栈帧称作“当前栈帧”(Current Stack Frame),这个栈帧所属的方法称作“当前方法”(Current Method)。
在执行main方法时,main方法所属的线程主线程,假设在主线程中调用了一个method1()方法,在method1()内部调用了method2()方法,在method2()方法执行两个整数运算,示例如下:
/**
* 方法调用
*
* @author iCode504
* @date 2023-10-23 22:05
*/
public class StackFrameDemo1 {
public static void main(String[] args) {
System.out.println("main开始执行");
method1();
System.out.println("main执行完成");
}
private static void method1() {
System.out.println("method1开始执行");
int result = method2();
System.out.println("result = " + result);
System.out.println("method1执行结束");
}
private static int method2() {
int var1 = 10;
int var2 = 20;
return var1 + var2;
}
}
运行结果:

由代码我们可以看出,main方法最先执行一个输出,然后进入method1执行第一个输出,再完整执行method2。method2执行完成以后,再执行method1,最后执行main方法,由于这段代码中只涉及一个主线程,并且最先完整执行方法的是method2,因此method2对应的栈帧就是当前栈帧,main方法最后执行完毕,因此main方法对应的栈帧在method2和method1之下。以下是这段代码对应的栈帧概念图:

在每一个栈帧中存储了方法的局部变量表、操作数栈、动态链接和方法返回地址等信息
1.1 局部变量表
局部变量表(Local variable Table)是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量。
局部变量表的容量是以变量槽(Variable Slot)为最小单位,每个变量槽能存储基本数据类型和引用数据类型的数据。为了尽可能节省栈帧消耗的内存空间,局部变量表中的变量槽是可以重用的。
JVM使用索引定位的方式使用索引变量表,索引值的范围是从0开始到局部变量表最大变量槽的数量(类似数组结构)。
当一个方法被调用的时候,JVM会使用局部变量表来完成参数值到参数变量列表的传递,即实参到形参的传递。
1.2 操作数栈
操作数栈(Operand Stack)也称作操作数栈,它是一个栈结构(后进先出,例如手枪的弹夹,先打出去的子弹是最顶上的子弹)。
在方法开始执行的时候,这个方法对应的操作数栈是空的,在方法执行过程中,会有各种字节码指令向操作数栈中写入或读取内容,即出栈和入栈操作,例如:两数相加运算时,就需要将两个数压入栈顶后调用运算指令。
操作数栈中的元素的数据类型必须和字节码指令序列严格匹配,在编译程序代码的时候编译器必须要严格保证这一点,在类的校验阶段的数据流分析时候还需要再次校验。例如:执行加法iadd(i是int类型,add是两个数相加)命令时,就需要保证两个操作数必须是int类型,不能出现其他类型相加的情况。
二、字节码分析(图解)
我们可以从字节码的角度进一步对i++和++i的执行过程做进一步的分析。以下面代码为例:
/**
* i++和++i的深入分析
*
* @author iCode504
* @date 2023-10-17 5:58
*/
public class IncrementAndDecrementOperators2 {
public static void main(String[] args) {
int intValue1 = 2;
int intValue2 = 2;
int result1 = intValue1++;
int result2 = ++intValue2;
System.out.println("result1 = " + result1);
System.out.println("result2 = " + result2);
}
}
我们需要查看编译后的字节码文件,字节码文件不能直接使用记事本打开,但是我们可以使用javap -verbose 文件名.class命令,以IncrementAndDecrementOperators2.class为例:
javap -verbose IncrementAndDecrementOperators2.class
此时就会打开所有的字节码文件,我们只需要关注main方法内的执行过程即可:

首先来解释一下这四行代码的含义:
0: iconst_2
1: istore_1
2: iconst_2
3: istore_2
iconst_2一共有两部分组成,i指的是int类型(源代码中我们定义的确实是int类型),const代表常量(数字2是整型常量),iconst_2的含义是将2入操作数栈。istore_1中的store代表的是存储,istore_1的含义是将操作数栈中的数值2出栈,存入到局部变量表1的位置。同理,i_store2表示将操作数栈中的数值2出栈,存储到局部变量表2的位置。
以下是前面四行代码存储过程图(存储过程全部流程图点击此链接下载:点我下载):




此时我们继续观察4-8行代码:
4: iload_1
5: iinc 1, 1
8: istore_3
iload_1的作用是将局部变量表1号位置存储的值移动到操作数栈的栈顶。- 第5行的
iinc有两个参数,第一个参数1是局部变量表的位置,另一个参数1的含义是在该位置存储一个1,如果这个位置存在值,那么这个值的结果是已存在值 + 参数值。 istore_3将操作数栈中的数移动到局部变量表的3号位置。
以下是这三行代码的示意图:



9-12行的字节码的作用原理和4-8行的作用原理基本相同:
9: iinc 2, 1
12: iload_2
13: istore 4
istore 4的作用是将操作数栈中的值存储到局部变量表4号位置。
以下是这三行代码的示意图:



接下来15-30行是和系统输出有关的。其中第30行iload_3在局部变量表中(这个值为2)值移动到操作数栈顶供系统输出,事实上iload_3的值正好对应源代码中变量result1的值。也就是说,result1输出结果就是iload_3的数值2。

同理,iload 4就是第二个要输出的值,在局部变量表中第4个位置存储的值正好是3,而输出的变量名是result2,因此result2的输出结果是3。

三、i++和++i性能分析
i++和++i主要用在普通for循环上,那么我们就将二者用在for循环上,循环相同的次数,从字节码的角度进行分析。
以下是使用i++和++i的两个for循环文件:
/**
* i++在for循环的使用
*
* @author ZhaoCong
* @date 2023-10-21 16:14:33
*/
public class LoopTest1 {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
}
}
}
/**
* ++i在for循环的使用
*
* @author ZhaoCong
* @date 2023-10-21 16:15:17
*/
public class LoopTest2 {
public static void main(String[] args) {
for (int i = 0; i < 100; ++i) {
}
}
}
执行编译命令以后,我们来查看两个文件的字节码:


仔细观察这两个字节码文件内容,我们发现在两个文件main方法的字节码内容完全相同。由此可见,两种方式执行for循环的效率是相同的。
入门篇-其之六-附录一-以Java字节码的角度分析i++和++i的更多相关文章
- JVM Java字节码的角度分析switch的实现
目录 Java字节码的角度分析switch的实现 引子 前置知识 一个妥协而又枯燥的方案 switch的实现 回顾历史 字节码分析 其他实现方式? Java字节码的角度分析switch的实现 作者 k ...
- i = i++ 在java字节码层面的分析
有这么一段代码: package zl.test; public class PcodeTest { /** * @param args */ public static void main(Stri ...
- Java字节码—ASM
前言 ASM 是什么 官方介绍:ASM is an all purpose Java bytecode manipulation and analysis framework. It can be u ...
- Java字节码分析
目录 Java字节码分析 查看字节码详细内容 javap 实例分析 Java字节码分析 对于源码的效率,但从源码来看有时无法分析出准确的结果,因为不同的编译器版本可能会将相同的源码编译成不同的字节码, ...
- 通过Java字节码发现有趣的内幕之String篇(上)(转)
原文出处: jaffa 很多时候我们在编写Java代码时,判断和猜测代码问题时主要是通过运行结果来得到答案,本博文主要是想通过Java字节码的方式来进一步求证我们已知的东西.这里没有对Java字节码知 ...
- 从 HelloWorld 看 Java 字节码文件结构
很多时候,我们都是从代码层面去学习如何编程,却很少去看看一个个 Java 代码背后到底是什么.今天就让我们从一个最简单的 Hello World 开始看一看 Java 的类文件结构. 在开始之前,我们 ...
- 在Eclipse里查看Java字节码
要理解 Java 字节码,比较推荐的方法是自己尝试编写源码对照字节码学习.其中阅读 Java 字节码的工具必不可少.虽然javap可以以可读的形式展示出.class 文件中字节码,但每次改动源码都需调 ...
- 【转】在Eclipse里查看Java字节码
要理解 Java 字节码,比较推荐的方法是自己尝试编写源码对照字节码学习.其中阅读 Java 字节码的工具必不可少.虽然javap可以以可读的形式展示出.class 文件中字节码,但每次改动源码都需调 ...
- Java字节码(.class文件)格式详解(一)
原文链接:http://www.blogjava.net/DLevin/archive/2011/09/05/358033.html 小介:去年在读<深入解析JVM>的时候写的,记得当时还 ...
- 空手套白狼,硬阅java字节码class文件
如下,是一些java字节码也就是原始的class文件,当应用部署到线上之后,我们能够看到的也就是这样的字样了.那么怎样解呢?就让我们一起,来解读解读字节码吧! Offset A B C D E F C ...
随机推荐
- ASL单芯片CS5366TypeC转HDMI4K60HZ加HUB多口方案|CS5366带PD拓展方案原理图
CS5366芯片是ASL集睿致远最新推出的2Len带PD的扩展坞方案芯片,CS5366支持4K60HZ. 在分辨率4K下,刷新率60HZ对于30HZ看似提升不多,但是对于应用在游戏主座的客户来说至关重 ...
- Sentieon | 每周文献-Genetic Disease-第二期
遗传病系列文章-1 标题(英文):Answer ALS, a large-scale resource for sporadic and familial ALS combining clinical ...
- Codeforces Round #888 (Div. 3) A-G
比赛链接 A 代码 #include <bits/stdc++.h> using namespace std; using ll = long long; bool solve() { i ...
- 2023-07-29:给你一个由数字组成的字符串 s,返回 s 中独特子字符串数量。 其中的每一个数字出现的频率都相同。
2023-07-29:给你一个由数字组成的字符串 s,返回 s 中独特子字符串数量. 其中的每一个数字出现的频率都相同. 答案2023-07-29: 大体步骤如下: 1.初始化变量base为固定值10 ...
- html元数据
元数据就是用来描述数据的数据.HTML中也有很多元数据. <meta>标签提供关于HTML文档的元数据:描述(description)\关键词(keywords).文档的作者(author ...
- QLabel类中的常用方法&信号
setAlignment: 按固定值方式对齐文本 Qt.AlignLeft:水平方向靠左对齐 Qt.AlignRight:水平方向靠右对齐 Qt.AlignCenter:水平方向居中对齐 Qt.Ali ...
- [gin]基于切片实现crud
前言 代码参考自<Building Distributed Application in Gin> 需求:设计一个食谱相关的API,数据存放到切片中. 设计模型和API 模型 type R ...
- nginx配置源IP访问控制
通过nginx的ngx_http_access_module模块,可实现对客户端的源IP地址进行允许或拒绝访问控制.该模块默认已编译. 允许访问指令 名称 允许访问指令 指令 allow 作用域 ht ...
- R2在全渠道业务线的落地
随着业务的增长,系统的高频率迭代,质量保障工作迫切需要引入更加科学高效的测试方法来助力业务高质量的交付.长城项目一期测试中,全渠道质量团队引入技术平台部R2技术,极大的提升了项目交付的质量.因此,本文 ...
- Python实现输入三个整数x,y,z,请把这三个数由小到大输出;
num1=input('请输入第一个数,x:') num2=input('请输入第二个数,y:') num3=input('请输入第三个数,z:') if num1>num2: # if 语句判 ...