跟着字节码追踪JAVA中的基本运算

看本文之前先推算一下下面这个程序的结果:

public class AddNum {

    public static void main(String[] args) {
        int i = 2;
        i = i++;
        int j = i++;
        int k = i + ++i + i++;
        System.out.println("i=" + i);
        System.out.println("j=" + j);
        System.out.println("k=" + k);
    }
}

运行结果如下:

 下面对着字节码指令,来看看每一步是怎样执行的,在idea中打开源文件对应的字节码文件

上图中绿色框框内是java文件中13~16行源码与字节码之间的对应关系,其实上面这段代码在jvm的内存模型中是属于线程私有的,对基本数据类型int的操作是在Java栈中进行的,我们知道在Java栈中有局部变量表,操作数栈,动态链接和方法出口等区域,而上面的数值计算部分就是在局部变量表和操作数栈这两块区域中进行的,所以在分析每一个字节码指令之前,先简单介绍一下jvm字节码中各个指令所代表的含义:

传送门🚪:http://www.gameboys.cn/article/96,这篇链接中详细介绍了Java中不同操作下的字节码,因为这次主要是分析基本数据类型的加法运算,所以我在这里只解释一下操作数栈和局部变量表操作中需要用到的字节码(备注:下图引自传送门中的文章):

再看我的代码所对应的字节码,其实主要用到了iconst_2、istore、iload、iadd、iinc这几个指令,它们所代表的意义分别是:

  • iconst_2:将int类型的数据2压如操作数栈的栈顶(入栈)
  • istore:将int类型的数据,从操作数栈栈顶出栈,并存储到局部变量表中(出栈)
  • iload:将Int类型的数据,从局部变量表中加载到操作数栈的栈顶(入栈)
  • iadd:对操作数栈中的栈顶的两个int类型的数据相加,并压如栈顶(出栈+入栈)
  • iinc:将指定的int类型的局部变量增加指定的值

根据上面的指令,再结合上面的字节码执行过程,梳理出代码中计算i、j、k的详细过程如下:

 下面来一步一步分析:

第一步:int i=2,即代码中的第13行

  • 将局部变量i存入局部变量表
  • 执行iconst_2指令,将int类型数值2压入操作数栈的栈顶
  • 执行istore指令,将操作数栈栈顶的数据2出栈,并赋值给局部变量i
  • 结果:i=2,操作数栈中没有数据了

第13行源码与字节码的对应关系如下:

 第二步:i=i++,即代码中的第14行

  • 执行iload指令,加载局部变量I的值,并将其压入操作数栈栈顶(2)
  • 执行iinc指令,局部变量i的值自增1,变成3
  • 执行istore指令,将操作数栈栈顶的元素(2)出栈,赋值给局部变量i
  • 结果:i=2,操作数栈中没有数据了

第14行源码与字节码的对应关系如下:

第三步:int j = i++,即代码中的第15行

  • 将局部变量j存入局部变量表
  • 执行iload指令,加载局部变量表中i的值,并压入操作数栈的栈顶(2)
  • 执行iinc指令,将局部变量i的值自增1,i变成3
  • 执行istore指令,将操作数栈栈顶的元素(2)出栈,并赋值给局部变量j
  • 结果:i=3,j=2,操作数栈中没有数据了

第15行源码与字节码的对应关系如下:

第四步:int k = i + ++i + i++,即代码中的第16行 

  • 将局部变量k存入局部变量表
  • 执行iload指令,加载局部变量表中的i,并将其值(3)压入操作数栈栈顶
  • 执行iinc指令,将局部变量表中的i自增1,i变成4
  • 执行iload指令,加载局部变量表中的i,并将其值(4)压入操作数栈栈顶
  • 执行iadd指令,取出操作数栈栈顶的两个int类型的数值(3和4),执行加法运算,将运算结果(7)压入操作数栈栈顶
  • 执行iload指令,加载局部变量表中的i,并将其值(4)压入栈顶
  • 执行iinc指令,将局部变量表中的i自增1,变成5
  • 执行iadd指令,取出操作数栈栈顶的两个int类型的数值(4和7),执行加法运算,将运算结果(11)压入操作数栈栈顶
  • 执行istore指令,将操作数栈栈顶的元素出栈,并赋值给局部变量k(11)
  • 结果:i=5,j=2,k=11,操作数栈中没有数据了

第16行源码与字节码的对应关系如下:

 上面就是程序中i、j、k计算部分的详细解读,主要容易出错的地方还是在i++和++i的使用上,自增操作要记住下面的规则:

i++和++i如果不存在赋值操作,都是对i自增1;

i++和++i如果存在赋值操作,i++是先取后加再赋值;++i是先加后取再赋值;

看似简单的运算其实并不简单,这就是了解底层原理的奇妙之处吧~~

posted @ 2021-11-22 11:35  bug改了我  阅读(76)  评论(0编辑  收藏  举报