本文内容来自《深入理解JAVA虚拟机》8.2节
栈帧是属于虚拟机栈的一部分,栈帧里存储了方法的入口,方法的参数,局部变量,返回地址等。栈顶的栈帧代表了当前正在执行的方法,每一个栈帧都又自己的操作栈,局部变量表等,大概5块数据区域。
1、局部变量表
存储方法里的局部变量,基础类型和引用类型。
虚拟机通过索引定位的方式使用局部变量表。一个对象的实例,局部变量表的第0位索引存储的就是这个实例的引用,也就是在实例方法中的可以调用的this。局部变量表的solt是可以共用的,因为不用的局部变量的范围不一定是整个实例。
局部变量表中的变量由于没有经历准备过程,因此不会被赋予默认的初始值。
局部变量的slot可以复用。会带来一些安全问题,见下方的代码解析。
【不再使用的对象设置为Null值有意义吗】
代码1:
main {
{
byte[] placeholder = new byte[1024];
}
System.gc();
}
代码2:
main {
{
byte[] placeholder = new byte[1024];
}
int a = 0;
System.gc();
}
请问两段代码执行完之后,placeholder所占用的内存被回收了吗?
答案是1没有被回收,2被回收了
为什么一个看起来毫无关联的int类型变量的定义可以让内存被回收?这源于局部变量表的Slot复用机制。Slot是局部变量表的变量槽,如果局部变量表是一个教室,一个个的Slot可以理解为教室中的一个个座位,Slot存储的是变量的索引,是可以复用的。以代码2来举例,placeholder被定义的时候,其引用的索引在局部变量表占据了一个Slot,也就是一个座位,代码执行到int a = 0时,被花括号包着的那段代码已经执行完毕,代码在花括号之外,也就意味着代码已经执行到placeholder的作用域之外了,此时placeholder已经是不可访问的变量,局部变量表中的索引可以被回收,int类型变量a被定义时,Slot复用了placeholder的位置,placeholder的索引从局部变量表中消失了。众所周知局部变量表是Gc Roots之一,从GCRoot的引用链中消失的placeholder自然也就被回收了。所以这就是为什么代码1执行完之后placeholder没有被回收,虽然已经出了作用域,但是从那之后到gc之前局部变量表没有发生任何的变化,placeholder的Slot还好端端的呆在里边,自然也就不会被回收。
这种情况只在局部变量占用了大量内存,执行完之后马上进行一个长耗时的操作的时候才会对内存有影响,只需要在编码时多加留意即可。
2、操作数栈
操作数栈是代码执行时存放操作数的栈,例如加法操作,先将两个参数从栈顶取出,计算得到结果以后,再将结果压入栈。操作数栈和局部变量表会有一部分重叠
3、动态连接
栈帧持有一个指向方法区常量池中所属方法的引用。这个引用是为了支持动态连接。动态连接是指符号引用在运行时转化为直接引用。
4、方法返回地址
方法的退出有两种方式,正常执行结束退出和异常退出。异常退出一般根据异常处理表来确定返回地址,栈帧中不会存储这一部分信息。方法的退出实际上就是将栈帧从栈里弹出,恢复上一层栈帧的本地变量表和操作数栈,并且将返回值压入上一栈帧的操作数栈。