JVM角度分析i++和++i的区别

Java虚拟机栈是什么?

  • Java虚拟机栈(Java virtual Machine stack),早期也叫Java栈。 每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(stack Frame), 对应着一次次的Java方法调用。

是线程私有的

  • 生命周期

生命周期和线程一致

  • 作用

主管Java程序的运行,它保存方法的局部变量(8种基本数据类型、对象的引用地址)、部分结果,并参与方法的调用和返回
局部变量vs成员变量(或属性)
基本数据变量vs 引用类型变量(类、数组、接口)

Java虚拟机栈内部结构

1.局部变量表 (Local Variables),局部变量数组
2.操作数栈 (Operand stack)(或表达式栈)
3.动态链接(Dynamic Linking)(或指向运行时常量池的方法引用)
4.方法返回地址(Return Address) (或方法正常退出或者异常退出的定义)
5.一些附加信息

局部变量表

1.定义一个数字类型的数组,主要用于存当前线程方法参数和定义方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用、以及返回地址
2.由于局部变量表是建立再虚拟机栈上的,是线程的私有数据,因此不存在数据的安全问题
3.局部变量表所需的容量大小是再编译期确定下来 ,并保存再方法的Code属性的maximum local Variables数据项中,再方法运行期间不会改变局部变量表的大小
4.方法嵌套调用 的次数由栈的大小决定。一般来说,栈越大,方法嵌套调用次数越多。 对于一个函数而言,他的参数和局部变量越多,使得局部变量表膨胀。
他的栈帧就越大,以满足方法调用所需传递的信息增大的需求。进而函数调用就会占用更多的栈空间,导致嵌套调用次数就会减少。
5.局部变量表中的变量只在当前方法调用中有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。
* 当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁

操作数栈

1.操作数栈,主要用于保存计算过程的中间结果,同时作为计算过程中变量
临时的存储空间。
2.操作数栈就是JVM执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建出来,这个方法的操作数栈是空的。
3.每一个操作数栈都会拥有一个明确的栈深度用于存储数值,其所需的最大
深度在编译期就定义好了,保存在方法的code属性中,为max_stack的值。
4.栈中的任何一个元素都是可以任意的Java数据类型。
–>32bit的类型占用一个栈单位深度
–>64bit的类型占用两个栈单位深度
5.操作数栈并非采用访问索引的方式来进行数据访问的,而是只能通过标准
的入校(push)和出栈(pop)操作来完成一次数据访问。

动态链接

1.每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接( Dynamic Linking)。比如: invokedynamic指令
2.在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用( symbolic Reference)保存在class文件的常量池里。比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。

i++和++i的区别

第一类问题

//i++方式
int i1 = 100;
System.out.println(i1++);
//++i方式
int i2 = 100;
System.out.println(++i2);

//i++方式字节码指令
 0 bipush 100 //JVM采用bipush指令将常量压入栈中【操作数栈】
 2 istore_1 //将一个数值从操作数栈存储到局部变量表
 3 getstatic #2 <java/lang/System.out>
 6 iload_1    //从局部表中的第一个变量的值出栈到操作数栈中
 7 iinc 1 by 1 //局部表中的第一个位置的变量按常量1增加 第一个位置1代表索引,第二个1表示常量
10 invokevirtual #3 <java/io/PrintStream.println>
 结果为: 100 
 
 //++i方式字节码指令
13 bipush 100
15 istore_2
16 getstatic #2 <java/lang/System.out>
19 iinc 2 by 1 //局部表中的第二个位置的变量按常量1增加 
22 iload_2 //从局部表中的第一个变量的值出栈到操作数栈中
23 invokevirtual #3 <java/io/PrintStream.println>
结果为: 101

第一类问题

//i++方式
int i3 = 100;
int i4 = i3++;
System.out.println("i4: "+i4);

//++i方式
int i5 = 100;
int i6 = ++i5;
System.out.println(i6);

//i++方式字节码文件
 0 bipush 100
 2 istore_1
 3 iload_1       //从局部表中的第一个变量的值出栈到操作数栈中
 4 iinc 1 by 1   //局部表中的第一个位置的变量按常量1增加
 7 istore_2      //将第2个数值从操作数栈存储到局部变量表
 8 getstatic #2 <java/lang/System.out>
11 iload_2     //从局部表中的第二个变量的值出栈到操作数栈中
12 invokevirtual #3 <java/io/PrintStream.println>
结果为:100

//++i方式字节码文件
15 bipush 10
17 istore_3
18 iinc 3 by 1 //局部表中的第3个位置的变量按常量1增加
21 iload_3     //从局部表中的第3个变量的值出栈到操作数栈中
22 istore 4    //将4个数值从操作数栈存储到局部变量表
24 getstatic #2 <java/lang/System.out>
27 iload 4
29 invokevirtual #3 <java/io/PrintStream.println>
结果为:101

第三类问题

//i++方式
int i7 = 10;
i7 = i7++;
System.out.println(i7);

//++i方式
int i8 = 10;
i8 = ++i8;
System.out.println(i8);

//i++方式字节码文件
 0 bipush 100
 2 istore_1
 3 iload_1
 4 iinc 1 by 1
 7 istore_1
 8 getstatic #2 <java/lang/System.out>
11 iload_1
12 invokevirtual #3 <java/io/PrintStream.println>
结果为:100

//++i方式字节码文件
15 bipush 100
17 istore_2
18 iinc 2 by 1
21 iload_2
22 istore_2
23 getstatic #2 <java/lang/System.out>
26 iload_2
27 invokevirtual #3 <java/io/PrintStream.println>
结果为:101

第4类问题

  int i9 = 100;
  int i10 = i9++ + ++i9;
  System.out.println("i10: "+i10);
  
 0 bipush 100
 2 istore_1
 3 iload_1      //从局部表中的第一个变量的值出栈到操作数栈中
 4 iinc 1 by 1 //局部表中的第一个位置的变量按常量1增加
 7 iinc 1 by 1 //局部表中的第一个位置的变量按常量1增加
10 iload_1   //从局部表中的第一个变量的值出栈到操作数栈中
11 iadd      //执行加加操作
12 istore_2
13 getstatic #2 <java/lang/System.out>
16 iload_2
17 invokevirtual #3 <java/io/PrintStream.println>
20 return
结果为:202

第5种没有赋值

//i++方式
int i1 = 10;
i1++;
//++i方式
int i2 = 10;
++i2;

//i++方式字节码文件
 0 bipush 10
 2 istore_1
 3 iinc 1 by 1
//++i方式字节码文件
 6 bipush 10
 8 istore_2
 9 iinc 2 by 1
 
 -- iload_1 除非从局部变量表将对应的信息,放到操作数栈才不同
 
 结果都为:10  操作数栈中的值还是10

重点

iload_1:从局部表中的第一个变量的值出栈到操作数栈中。
相当于从局部表中读了第一个数据然后存到操作表中。可以这么简单的理解。
区别就在于
i++是直接将操作表中数据存到局部表中。
而++i是先读取的局部表中的数据到操作栈,然后在从操作栈存到局部表中。
为什么这样做?主要是因为iinc这个命令,这个命令是操作的是局部表。
i++:
iinc 执行之前是 局部表中变量值是100的而操作栈里的值也是100.
但是执行完iinc命令后是局部表中值变成101,而操作栈中的值依然是100;最后执行istore局部表变100.
++i:
iinc执行之前是局部表中变量值是100,而操作栈里是空的。
iinc执行完后局部表中是101,由于执行了iload操作栈也是101.最后执行了istore最终局部表变101

结论

i++和++i最终的区别其实就在于有没有去读局部表中的变量的值。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值