前言:
栈:栈是jvm内存之一,被线程独享,不存在线程安全问题。当一个线程被创建时,jvm会随之为其创建一个栈。
栈桢:栈桢是栈的元素。当一个方法被调用时,jvm会为其创建一个栈桢。方法中的各种处理,便是一个出栈入栈的过程。
栈桢的结构:栈桢由四部分组成,分别是局部变量表、操作栈、动态链接、返回地址。
局部变量表:
顾名思义,局部变量表是用来存储局部变量的。局部变量包含两部分,一是方法中的参数,二是方法中创建的局部变量。局部变量必须被初始化才能使用。
既然是存储变量,那么就有最小存储单位。那么局部变量表的最小存储单位是什么呢?答案是变量槽(Slot)。Slot是多少位的呢?如果你的操作系统是32位
的,那么Slot就是32位的。操作系统是64位的,Slot便是64位的。以32位操作系统为例,一个Slot可以存放一个32位以内的数据类型,比如boolean、byte、
char、short、int、float、reference和returnAddress。引申一下,对于long、double八字节、64位的数据,必然要用两个Slot来存储,那么运算时会产生
线程安全问题吗?大家可以思考一下。
Slot复用:
内存是非常宝贵的资源,jvm是能省则省。所以对于Slot来说,它是被设计成可以复用的。什么意思呢?就是当前Slot存储一个变量后,下一秒这个Slot
就会存储其他类型的变量。那么被复用的条件是什么呢?条件是:当Slot中的变量超出了作用域,那么下一次分配Slot的时候,将会覆盖原来的数据。Slot对对
象的引用会影响GC(要是被引用,将不会被回收,这才是重点,不然费劲说局部变量表干嘛)。下面以实例来解释一下。
实战1: 创建一个大对象和一个整型变量,两者作用域相同。看jvm是否会回收大对象。
左图是代码和gc日志,右图是反编译结果。从左图可以看出,array对象并没有被回收。为什么呢?因为array变量的作用域是当前作用域,还有被使用的可能(虽然当前
代码里没有),jvm当然不会回收他。变量槽Slot也不会被复用。右图locals=3表示什呢?表示该栈桢中,有三哥局部变量,分别是方法参数args、array、a。
实战2:创建一个大对象和一个整型变量,但大对象作用域是在{}中,而整型变量作用域是当前作用域 。看jvm是否会回收大对象。
图1
图2
从图2可以看出,内存使用量明显被降低,证明array对象被回收。对比图1和图2,唯一区别在于图2代码中多了一行 int a = 10, 但结果完全不一样呢?
这是为什么呢?我们分析一下,当array变量被创建之后,jvm便会分配一个变量槽Slot存储它,这个Slot存储着byte数组堆内存的地址,即引用。也就是
说目前这个array对象虽然不再被使用,但它仍然是根对象可达的,不会被jvm划为垃圾,进而被回收。但是当执行int a = 10的时候,jvm发现array变量
的作用域已经结束了,这个array变量不会再被使用了。闲着也是闲着,就把存储array变量的变量槽Slot征用了。这时,jvm发现堆内存中的array对象是
根对象不可达的了,不存在对它的引用了。标记为垃圾内存,回收一波。
内存图大致画一下,意思意思:
实战3:创建一个大对象,大对象作用域是在{}中,且使用完大对象之后,立马赋值null 。看jvm是否会回收大对象。
从上图可以看出,array对象被回收了。
综上,我们可以得出这样一个结论:当在方法中创建一个大对象,使用完之后,最好立马赋值为null,便于jvm对其进行回收。
以上是个人认知,如有不足,请指正。