栈帧中都有啥东西

大纲

前言


个人全部文章同步更新与Github--Java-Notes,想了解JVM,HashMap源码分析,spring相关,剑指offer题解(Java版),能够点个star。能够看个人github主页,天天都在更新哟。java

邀请您跟我一同完成 repogit


栈帧是虚拟机栈的一个单位,以前讲解了运行时数据区和类加载的过程,如今咱们看下虚拟机中栈帧都是啥样子的,这个应该算是运行时数据区(JVM内存结构的补充),若是不了解能够参考个人这篇博文 JVM内存结构github

运行时栈帧结构

运行时栈帧中存储了如下内容spring

  • 局部变量
  • 操做数栈
  • 动态连接
  • 返回地址
  • 附加信息
  • ….

每个方法的调用开始和结束都是栈的压入(入栈)和弹出(出栈)的过程数组

局部变量表

是什么

局部变量表是一组变量值存储空间,用于存放方法参数方法内部定义的局部变量工具

大小是编译的时候写进了字节码里面的,在Code属性中的max_local属性,即下面的local源码分析

有什么

局部变量表的容量以变量槽(Variable Slot)为最小单位,虚拟机中并无明确指明一个Slot应占用的内存空间大小,只是颇有导向性的说到每一个Slot都应该能存放一个下面8种类型的其中一个。(若是是long或者double这种64位的数据类型,则须要两个Slot)spa

  • boolean
  • byte
  • char
  • short
  • int
  • float
  • reference
  • returnAddress

前六种应该不用说,是基本的数据类型,reference是啥呢code

reference

reference是一个对象实例的引用cdn

做用:

  • 今后引用中直接或间接查找对象在Java堆中的数据存放的起始地址索引
  • 今后引用中直接或间接查找到对象所属数据类型在方法区中的存储的类型对象(由于类信息在方法区中存储)

returnAddress

为字节码指令jsr、jsr_w和ret提供的,指向了一条字节码指令的地址

已经不多见了。

注意

局部变量表中的局部变量和以前将类加载的时候的类变量(static修饰)不同,他没有所谓的"准备阶段",因此没有设置初始值的阶段。不知道的能够参考我类加载这篇文章,看了准备阶段,应该就知道了。类加载过程

因此咱们在写程序的时候这样写,对比你就知道了

其余类型零值

Slot复用

不使用的对象,应当手动赋值为null

为了尽量节省栈空间,局部变量表的Slot能够复用。方法体中定义的变量,其做用域并不必定覆盖整个方法体,若是当前字节码PC计数器的值已经超出了某个变量的做用域,那这个变量对应的Slot就能够交给其余变量使用。

不过这样的作法,会有一些缺点,咱们来看下面的代码示例

public class Test2 {

    public static void main(String[] args) {
        

            byte[] placeholder = new byte[64 * 1024 * 1024];


        
        System.gc();
    }
}
复制代码

咱们经过配置虚拟机参数-verbose:gc来打印垃圾回收的结果

咱们看到他并无回收。

咱们修改一下代码

public class Test2 {

    public static void main(String[] args) {
        {

            byte[] placeholder = new byte[64 * 1024 * 1024];


        }
        System.gc();
    }
}
复制代码

他仍是没有进行回收,按理说 placeHolder 的做用域只在花括号中,在执行gc方法的时候,他就已经不可能用了,算是已经"死"了的对象了,为何没有回收呢?

咱们再修改一下

public class Test2 {

    public static void main(String[] args) {
        {

            byte[] placeholder = new byte[64 * 1024 * 1024];


        }
      	int  a = 0;
        System.gc();
    }
}
复制代码

咱们看到,此次垃圾回收器工做了,为何呢?

placeholder 可否被回收的根本缘由是:局部变量表的Slot是否还存有关于placeholder数组对象的引用。

第一次修改中,代码虽然离开了该变量的做用域,可是在此以后,没有任何对局部变量表的读写操做,该变量本来占用的Slot尚未被任何其余变量复用,因此做为GC Root 一部分的局部变量表仍然保持着对他的关联(不了解什么能够做为GC Root的话,能够参考个人这篇文章 JVM垃圾回收)

而第二次,则改变了上面的这种状况

因此当遇到一个方法,其后面的代码有一些耗时很长的操做,而前面又定义了占用大量内存、实际上已经用不到的变量,应当手动设置其为null。

不少工具类都有这个操做,好比 ArrayList和Stack中的remove方法,你也能够找下其余的工具类中的方法,看是否有此类操做

操做数栈

操做数栈记录了一个方法执行过程当中的字节码指令,他往操做数栈中进行入栈和出栈

大小在编译的时候也已经肯定了,字节码文件中的Code属性中的max_stacks数据项,即下图的stack

当一个方法刚执行的时候,操做数栈是空的,在方法执行的过程当中,会有各类字节码指令往操做数栈中入栈和出栈。

动态链接

每个栈帧都包含一个指向运行时常量池中该栈帧所属的方法的引用,持有这个引用是为了支持方法调用过程当中的动态链接

若是你看了字节码文件构成类加载过程,你应该知道,字节码文件中有不少符号引用。

这些符号引用一部分会在类加载的解析阶段或者第一次使用的时候转化为直接引用,这种转化称为静态解析

另外一部分会在运行期间转化为直接引用,这部分称为动态链接

动态链接会在这篇文章 方法调用 中讲解

返回地址

一个方法执行后,只有两种方法能够退出:

  • return,正常退出
  • 异常,而且不在该方法中处理

方法退出时可能的操做:

  • 恢复上层方法的局部变量表和操做数栈
  • 把返回值(若是有的话)压入调用者战争的操做数栈中
  • 恢复PC计数器的值,以指向方法调用指令后面的一条指令

附加信息

这部分信息彻底取决于具体的虚拟机实现,有的可能有,有的没有。

总结

  • 栈帧中主要有4种信息
    • 局部变量表
      • 单位Slot
      • Slot复写
    • 操做数栈
      • 方法执行时,入栈出栈各类字节码指令
    • 动态链接
      • 运行期间,将符号引用转化为直接引用
    • 返回地址
      • return
      • 异常而且没有在该方法中处理
    • ….
相关文章
相关标签/搜索