转载

栈帧中都有啥东西

我的所有文章同步更新与Github-- Java-Notes ,想了解JVM,HashMap源码分析,spring相关,剑指offer题解(Java版),可以点个star。可以看我的github主页,每天都在更新哟。

邀请您跟我一同完成 repo

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

运行时栈帧结构

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

  • 局部变量
  • 操作数栈
  • 动态链接
  • 返回地址
  • 附加信息
  • ….
栈帧中都有啥东西

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

局部变量表

是什么

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

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

栈帧中都有啥东西

有什么

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

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

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

reference

reference是一个对象实例的引用

作用:

  • 从此引用中 直接或间接查找对象在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

栈帧中都有啥东西

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

动态连接

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

如果你看了 字节码文件构成 和 类加载过程 ,你应该知道,字节码文件中有很多符号引用。

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

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

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

原文  https://juejin.im/post/5cd593c06fb9a0322a1f917b
正文到此结束
Loading...