由虚拟机栈搞懂i++与++i

1.java虚拟机栈

简单介绍一下,在JVM中的运行时数据区分为五大区域,分别是程序计数器、java虚拟机栈、本地方法栈、堆和方法区。

而今天我们主要讲的就是java虚拟机栈。这一块区域是线程私有的,也就是每一个线程就是一个栈,在这个栈里存放的元素就是栈帧,这个栈帧说白了就是方法,每一个栈帧的入栈出栈就是一次方法的执行。在栈帧里主要有局部变量表,操作数栈,动态链接和方法出口。

由虚拟机栈搞懂i++与++i_第1张图片

了解这个东西离不开代码,我们先来定义一个简单地类,就让他实现两数相加的功能

public class Number {    
    public static void main(String[] args) {        
        int a = 1;        
        int b = 2;        
        int c = a+b;    
    }
}

这个代码很简单,但我们想看到内部是如何实现的,现在我们对他进行反编译。

javac Number.java  //生成class文件,文件位置与类在同一目录下
javap -c Number.class   //对class文件进行反编译

通过这两个命令我们就能得到我们想要的信息,下面来看一下

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

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1    	//int型常量值1进栈
       1: istore_1    	//将栈顶int型数值存入第一个局部变量(a)
       2: iconst_2		//int型常量值2进栈
       3: istore_2		//将栈顶int型数值存入第二个局部变量(b)
       4: iload_1       //指定的int型局部变量进栈,这里就是a
       5: iload_2		//指定的int型局部变量进栈,这里就是b
       6: iadd			//栈顶两int型数值相加,并且结果进栈
       7: istore_3		//将栈顶int型数值存入第二个局部变量(c)
       8: return		
}

在上面所说的栈就是操作数栈, 很明显,对数据的操作必须依赖于操作数栈,变量存放在局部变量表里(这个从名字也能看出来好吧!),下面我们用图解的方式解释一下具体的操作。

由虚拟机栈搞懂i++与++i_第2张图片

首先会将int类型的数字1压入操作数栈,随后出栈并赋值给变量a,这样就完成了一次对变量a的赋值。同理,对b的赋值也是如此。

由虚拟机栈搞懂i++与++i_第3张图片

随后进行a+b的操作,首先他会将a和b的值依次进入操作数栈,然后再将1和2的结果相加并再次将结果压入栈中,这时操作数栈中就有一个int类型数据3,再将3出栈赋值给c,完成相加操作。

2.i++与++i

那我们知道了底层的操作之后是不是也能弄懂i++++i到底是怎么回事了!下面就让我们一起来探索一下。

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

很简单的小例子,如果你的面试官问你,你认为结果应该输出多少?如果你脱口而出10,那么凉凉,面试官眉头一皱,你感觉事情没那么简单。那么让我们一步步将这个小东西剖析开来。

我们知道a++是先赋值,再自增,拆解成代码也就是 a = a; a = a +1;按照道理来说结果应该是10,因为每次都自增了,那为什么不是呢?

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

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iconst_0
       3: istore_2
       4: iload_2
       5: bipush        10
       7: if_icmpge     21
      10: iload_1
      11: iinc          1, 1		//指定int型变量增加指定值(增加之后的结果,增加值)
      14: istore_1
      15: iinc          2, 1
      18: goto          4
      21: getstatic     #2          // Field java/lang/System.out:Ljava/io/PrintStream;
      24: iload_1
      25: invokevirtual #3          // Method java/io/PrintStream.println:(I)V
      28: return
}

这里我们主要看10、11、14,我们可以看到在执行a = a++时,它先把a的值压入操作数栈,这时候a的值是0,然后执行了iinc指令,注意,这时候局部变量表中的a的值发生了改变,变成了1。但是!但是!操作数栈中的数还是0!然后执行了什么命令呢?他执行了istore_1它把操作数栈中的0赋值给了a!!!这就明白了为啥最后输出是0了。

由虚拟机栈搞懂i++与++i_第4张图片

搞懂了i++,那++i自然也就懂了,与上面类似,只不过是先执行自增,再入栈出栈。至此我们通过剖析底层操作原理终于搞懂了这两者的区别!

闲谈

到了这我还有一个疑问(与上面的没关系,我自己瞎想的),那我定义一个非静态变量为什么不能再静态方法里使用(别忘了,main方法也是静态方法),这就要牵扯到数据在内存存储的具体情况了。

如果是一个类的静态变量,那么这个变量存放在方法区里,而对于普通变量是必须要存放在实例中的,是存放在堆里面的,在栈帧中还有一个动态连接,这个就是指向这个栈帧所执行的方法,我们能通过这个引用找到这个类,并能获取这个类的类信息,自然也就能找到静态变量。而我们如果不新建一个类的实例的话,那么将没有这个普通变量,自然也就找不到,用不了。

你可能感兴趣的:(JVM)