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

Java 有趣的程序编译

程序员文章站 2022-06-14 22:01:59
今天看到一道题,很有意思,特此记录一下。public class Test { public static void main(String args[]) { int a = 0; int b = 0; while(a < 10){ b = b++; a++; } System.out.println(b); }}大脑编译一下,直觉告诉我每次循环...

今天看到一道题,很有意思,特此记录一下。

public class Test {
    public static void main(String args[]) {
        int a = 0;
        int b = 0;
        while(a < 10){
            b = b++;
            a++;
        }
        System.out.println(b);
    }
}

大脑编译一下,直觉告诉我每次循环b都加了两次,但总觉得哪里不对,运行出来发现结果是0,也让我百思不得其解,于是又一顿疯狂搜索学习,最后弄明白了原理,遂写一篇博客,让有兴趣的都可以看看。

上面这个代码用断点debug能看到每次循环b都是0,并没有自增,但断点并不能告诉我们为什么,只有用反汇编的手段才能把它彻底弄明白。不过在看反汇编的代码前,先弄明白什么是局部变量表、什么是操作数栈比较好。

JVM虚拟机作为提供java程序的运行环境,它在运行时,其内存被划分为了几大板块:程序计数器、虚拟机栈、本地方法栈、堆、方法区

我们要了解的局部变量表和操作数栈都是属于虚拟机栈模块,其它模块有兴趣的自行学习吧,我也不甚精通。

虚拟机栈模块中,有一个很重要的数据结构叫做“栈帧”,你可以把它理解为虚拟机栈中的其中一个栈元素,每个栈帧包含了四个东西:局部变量表、操作数栈、动态链接、返回地址

局部变量表用于存储方法中的局部变量和方法中的参数,可以理解为是一个数组结构。

操作数栈作为每条指令的工作区域,指令对数据的操作都要经过操作数栈的入栈出栈来实现。

剩余两个这里就不展开细说了。

上述知识均来自《深入理解Java虚拟机第3版》

有了上面的知识后,直接对编译后的class字节码文件进行javap -c反汇编得到下面的代码。为了方便理解,我对关键部分做了中文注释。如果要具体了解每个指令的用处,请自行搜索“JVM指令集”

Compiled from "Test.java"
public class test.Test {
  public test.Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0                          //将0压入操作数栈顶部
       1: istore_1                          //弹出操作数栈顶元素,保存到局部变量表第1个位置
       2: iconst_0                          //将0压入操作数栈顶部
       3: istore_2                          //弹出操作数栈顶元素,保存到局部变量表第2个位置
       4: iload_1                           //将局部变量表第1个位置的值(也就是a的值)压入操作数栈顶部
       5: bipush        10                  //将10压入到操作数栈顶
       7: if_icmpge     21                  //比较操作数栈顶两int型数值大小,当结果等于0(相等)时跳转到21条指令执行,比较完成后清空栈顶两元素
      10: iload_2                           //将局部变量表第2个位置的值(也就是b的值)压入操作数栈顶部
      11: iinc          2, 1                //将局部变量表第2个位置的值(也就是b的值)进行+1操作
      14: istore_2                          //弹出操作数栈顶元素,保存到局部变量表第2个位置(这是关键,这里的0将原先+1后的b给覆盖了,后面每次循环都被0覆盖了)
      15: iinc          1, 1                //将局部变量表第1个位置的值(也就是a的值)进行+1操作
      18: goto          4                   //无条件跳转到第4条指令
      21: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      24: iload_2
      25: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      28: return
}

可以看到最关键的是第14条指令,每次循环都是先将b的值获取并存放到操作数栈,然后通过iinc指令直接将局部变量表中的b加1,再又将操作数栈中b原先的值0覆盖到局部变量表中的b,所以b最终还是0。

后面有时间再来做个动态图演示吧。

Java 有趣的程序编译

 

本文地址:https://blog.csdn.net/c_o_d_e_/article/details/112283353