欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

从反编译的角度浅谈JVM如何执行i++和++i操作

程序员文章站 2022-03-16 14:56:51
之前对于i++和++i的理解比较浅显。通常对i++的表述为先赋值再自增,对于++i的表述为先自增再赋值。鉴于之前对i++的原子性问题不能完全理解,并且在最近的笔试中,发现其有很多玩法。对于JVM如何进行这两个操作有必要研究一下。注意:以下i均以整型为例!《疯狂Java讲义》中关于++运算符的描述《疯狂Java讲义》(第4版)中对于++运算符的描述如下:++:自加。该运算符有两个要点:1.自加是单目运算符,只能操作一个操作数;2.自加运算符只能操作单个数值型(整型、浮点型都行)的变量,不能操作常量或...

之前对于i++和++i的理解比较浅显。通常,对i++的表述为先赋值再自增,对于++i的表述为先自增再赋值。鉴于在学习JUC的原子变量时,对i++的原子性问题不能完全理解,并且在最近的笔试中,发现其有很多玩法。因此,对于JVM如何执行这两个操作有必要研究一下。对小问题的探究有助于加深对java理解。
注意:以下i均以整型为例!

一、 《疯狂Java讲义》中关于++运算符的描述

《疯狂Java讲义》(第4版)中对于++运算符的描述如下:

++:自加。该运算符有两个要点:1.自加是单目运算符,只能操作一个操作数;2.自加运算符只能操作单个数值型(整型、浮点型都行)的变量,不能操作常量或表达式。运算符既可以出现在操作数的左边,也可以出现在操作数的右边。但出现在左边和右边的效果是不一样的。如果把++放在左边,则先把操作数加1,然后才把操作数放入表达式中运算;如果把++放在右边,则先把操作数放入表达式中运算,然后才把操作数加1。看如下代码:

int a = 5;
// 让a先执行算术运算,然后自加
int b = a++ + 6;
// 输出a的值为6,b的值为11
System.out.println(a + "\n" +b);

执行完后,a的值为6,而b的值为11。当++在操作数右边时,先执行a + 6的运算(此时a的值为5),然后对a加1。对比下面代码:

int a = 5;
// 让a先自加,然后执行算术运算
int b = ++a + 6;
// 输出a的值为6,b的值为12
System.out.println(a + "\n" +b);

执行的结果是a的值为6,b的值为12。当++在操作数左边时,先对a加+,然后执行a + 6的运算(此时a的值为6),因此b为12.

二、 局部变量表和操作数栈

对i的运算是在虚拟机栈中进行的。首先,介绍虚拟机栈中与i的运算有关的两个概念:局部变量表和操作数栈。

局部变量表(Local Variable Table)
在编译程序代码的时候就可以确定栈帧中需要多大的局部变量表,具体大小可在编译后的 Class 文件中看到。局部变量表的容量以 Variable Slot(变量槽)为最小单位,每个变量槽都可以存储 32位长度的内存空间。在方法执行时,虚拟机使用局部变量表完成参数值到参数变量列表的传递过程的,如果执行的是实例方法,那局部变量表中第 0位索引的 Slot 默认是用于传递方法所属对象实例的引用(在方法中可以通过关键字 this来访问到这个隐含的参数)。其余参数则按照参数表顺序排列,占用从 1 开始的局部变量 Slot。基本类型数据以及引用和returnAddress(返回地址)占用一个变量槽,long 和 double 需要两个。

操作数栈(Operand Stack)
同样也可以在编译期确定大小。 Frame 被创建时,操作栈是空的。操作栈的每个项可以存放 JVM 的各种类型数据,其中 long 和 double 类型(64位数据)占用两个栈深。 方法执行的过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈和入栈操作(与Java 栈中栈帧操作类似)。操作栈调用其它有返回结果的方法时,会把结果 push 到栈上(通过操作数栈来进行参数传递)。

从反编译的角度浅谈JVM如何执行i++和++i操作

三、JVM如何执行++运算符

下面通过四个用例介绍jvm是如何执行i++和++i操作的。

1、i = i++;问题

以下面代码为例:

public class AutoIncrement {
    public static void main(String[] args) {
        int i = 1;
        i = i++;
        System.out.println(i);
    }
}

输出结果为:1。
我们来反编译下,看看JVM是如何执行这段代码的操作的。
反编译结果如下:
从反编译的角度浅谈JVM如何执行i++和++i操作
从反编译后的结果可以看出,int i = 1;是如下两步操作,即:
0: iconst_1 —— 把常量1压入操作数栈;
1: istore_1 —— 把栈顶元素1存入局部变量表的第一个变量i中;

i = i++;是如下三步操作:
2: iload_1 —— 把第一个变量i的值(即i的值1)压入栈中;
3: iinc 1, 1 —— 第一个变量i的值加1(此时变量i的值为2);
6: istore_1 —— 把栈顶元素1存入到第一个变量i中(此时变量i的值又为1)。
通过以上不难看出,i = i++;的操作实际上分为三个步骤“读-改-写”(涉及i++的原子性问题),可以理解为:

int temp = i;
i = i + 1;
i = temp;

2、i = i++ + i;问题

以下面代码为例:

public class AutoIncrement {
    public static void main(String[] args) {
        int i = 1;
        i = i++ + i;
        System.out.println(i);
    }
}

输出结果为:3。
我们来反编译下,看看JVM是如何执行这段代码的操作的。
反编译结果如下:
从反编译的角度浅谈JVM如何执行i++和++i操作
从反编译后的结果可以看出,i = i++ + i;是如下五步操作:
2: iload_1 —— 把第一个变量i的值(即i的值1)压入栈中;
3: iinc 1, 1 —— 第一个变量i的值加1(此时变量i的值为2);
6:iload_1 —— 把第一个变量i的值(即i的值2)压入栈。
7: iadd —— 取出栈中的top元素和top-1元素进行相加得3,并将结果压入栈中;
8: istore_1 —— 把栈顶元素3存入到第一个变量i中(此时变量i的值为3)。

3、i = ++i;问题

以下面代码为例:

public class AutoIncrement {
    public static void main(String[] args) {
        int i = 1;
        i = ++i;
        System.out.println(i);
    }
}

输出结果为:2。
我们来反编译下,看看JVM是如何执行这段代码的操作的。
反编译结果如下:
从反编译的角度浅谈JVM如何执行i++和++i操作
从反编译后的结果可以看出,i = ++i;是如下三步操作:
2: iinc 1, 1 —— 第一个变量i的值加1(此时变量i的值为2);
5: iload_1 —— 把第一个变量i的值(即i的值2)压入栈;
6: istore_1 —— 把栈顶元素2存入到第一个变量i中(此时变量i的值为2)。
以上操作可以理解为:

i = i + 1;
int temp = i;
i = temp;

4、i = ++i + i;问题

以下面代码为例:

public class AutoIncrement {
    public static void main(String[] args) {
        int i = 1;
        i = ++i + i;
        System.out.println(i);
    }
}

输出结果为:4。
我们来反编译下,看看JVM是如何执行这段代码的操作的。
反编译结果如下:
从反编译的角度浅谈JVM如何执行i++和++i操作
从反编译后的结果可以看出,i = ++i + i;是如下五步操作:
2: iinc 1, 1 —— 第一个变量i的值加1(此时变量i的值为2);
5: iload_1 —— 把第一个变量的i值(即i的值2)压入栈;
6:iload_1 —— 把第一个变量的i值(即i的值2)压入栈。
7: iadd —— 取出栈中的top元素和top-1元素进行相加得4,并将结果压入栈中;
8: istore_1 —— 把栈顶元素4存入到第一个变量i中(此时变量i的值为4)。

四、总结

通过以上四个用例,我们不难发现,jvm在执行i++时是先将局部变量表中变量i(以i=1为例)的值先压入操作数栈中,然后再将局部变量表中变量i值加1,这时i的值为2。而整个i++用于运算的是已经压入操作数栈中的1,即使用自加前i的值。若i++之后有关于i的运算,如++i + i,再将局部变量表中的变量i(此时i的值为2)压入操作数栈中,即使用自加后i的值。而jvm在执行++i时(同样以i=1为例),是先将局部变量表中的变量i值加1。此时i的值为2。然后再将变量i的值压入操作数栈中。这时整个++i操作用于运算的是已经完成自加1操作的值。若++i之后有关于i的运算,如i++ + i,i同样使用的是已经完成自加的值(i=2)。
通过以上分析,对jvm如何执行++操作有了更深的理解。在今后的工作或笔试中遇到类似i = i++ + i + ii = i++ + ++i + i等问题都知道如何应对了。

本文地址:https://blog.csdn.net/m0_47503416/article/details/109921158