​​Java​​栈概述

 

  

帧为单位保存当前线程的运行状态。某个线程正在执行的方法称为当前方法,当前方法使用的栈帧称为当前帧,当前方法所属的类称为当前类,当前类的常量池称为当前常量池。当线程执行一个方法时,它会跟踪当前常量池。

当执行这个方法时,它使用这个帧来存储参数、局部变量、中间运算结果等等。

java栈上的所有数据都是私有的。任何线程都不能访问另一个线程的栈数据。所以我们不用考虑多线程情况下栈数据访问同步的情况。

像方法区和堆一样,java栈和帧在内存中也不必是连续的,帧可以分布在连续的栈里,也可以分布在堆里

 

java栈的组成元素——栈帧

 

栈帧由三部分组成:局部变量区、操作数栈、帧数据区。局部变量区和操作数栈的大小要视对应的方法而定,他们是按字长计算的。但调用一个方法时,它从类型信息中得到此方法局部变量区和操作数栈大小,并据此分配栈内存,然后压入Java栈。

 

局部变量区

     说再多也没用,下面就看个例子,好让大家对局部变量区有更深刻的认识。这个图来着《深入JVM》:

      


Java代码   


1. public static int runClassMethod(int i,long l,float f,double d,Object o,byte b) {
2. return 0;
3. }
4.
5. public int runInstanceMethod(char c,double d,short s,boolean b) {
6. return 0;
7. }

 

    上面代码片的方法参数和局部变量在局部变量区中的存储结构如下图:

    

栈和局部变量_局部变量


      

上面这个图没什么好说的,大家看看就会懂。但是,在这个图里,有一点需要注意:

runInstanceMethod的局部变量区第一项是个reference(引用),它指定的就是对象本身的引用,也就是我们常用的this,但是在runClassMethod方法中,没这个引用,那是因为runClassMethod是个静态方法。

   

    操作数栈  和局部变量区一样,操作数栈也被组织成一个以字长为单位的数组。但和前者不同的是,它不是通过索引来访问的,而是通过入栈和出栈来访问的。可把操作数栈理解为存储计算时,临时数据的存储区域。下面我们通过一段简短的程序片段外加一幅图片来了解下操作数栈的作用。

int

int

int

     

栈和局部变量_操作数_02


 

    从图中可以得出:操作数栈其实就是个临时数据存储区域,它是通过入栈和出栈来进行操作的。

  

   

帧数据区   除了局部变量区和操作数栈外,java栈帧还需要一些数据来支持常量池解析、正常方法返回以及异常派发机制。这些数据都保存在java栈帧的帧数据区中。

    当JVM执行到需要常量池数据的指令时,它都会通过帧数据区中指向常量池的指针来访问它。

如果方法又返回值,JVM会把返回值压入到发起调用方法的操作数栈。

然后再发起调用方法的上下文重新抛出同样的异常。

 

   

   栈的整个结构

  

  在前面就描述过:栈是由栈帧组成,每当线程调用一个java方法时,JVM就会在该线程对应的栈中压入一个帧,而帧是由局部变量区、操作数栈和帧数据区组成。那在一个代码块中,栈到底是什么形式呢?下面是我从《深入JVM》中摘抄的一个例子,大家可以看看:

 

   代码片段:

   

栈和局部变量_JVM_03

  

     执行过程中的三个快照:

    

栈和局部变量_JVM_04


 

  上面所给的图,只想说明两件事情:

   1.  只有在调用一个方法时,才为当前栈分配一个帧,然后将该帧压入栈

调用 方法的帧的操作数栈中