JVM局部变量表
局部变量表是JVM线程栈中每个frame中一个组成单元(具体细节见《JVM线程栈》),存放线程在当前方法执行过程中依然有效的局部变量。局部变量表的长度在类编译过程中就能确定,这样有利于frame初始化。
void fun () {
int a = 0;
int b = 1;
int c = 2;
}
fun的局部变量表长度是4(依照JVM规范,第一个是this指针)
void fun2() {
{
int a = 0;
}
int b = 1;
int c = 2;
}
fun的局部变量表长度是3,这是因为局部变量表中的槽位(slot)可以被复用。当程序走到b=1时,a变量的声明周期就已经结束了,此时b变量会占用a变量的操作。因为slot复用的特点,会引出一个很有意思的问题。
public static void main(String[] args) throws Exception {
{
byte[] _64M = new byte[1024 * 1024 * 64];
}
System.gc();
Thread.sleep(1000);
System.gc();
Integer a = null;
System.gc();
}
使用-verbose:gc把gc log打出来可以看到:
[GC (System.gc()) 67502K->66452K(123904K), 0.0011957 secs]
[Full GC (System.gc()) 66452K->66331K(123904K), 0.0053644 secs]
[GC (System.gc()) 66987K->66363K(123904K), 0.0007278 secs]
[Full GC (System.gc()) 66363K->66331K(123904K), 0.0055632 secs]
[GC (System.gc()) 66331K->66331K(123904K), 0.0011248 secs]
[Full GC (System.gc()) 66331K->795K(123904K), 0.0050505 secs]
第一次gc没有回收掉64M的数组,但_64M的生命周期已经结束了,睡了1s还是回收不掉,对空间就这样被占用了1s。
解释这个现象要从JVM回收对象的条件开始。JVM要回收局部变量的前提是局部变量表中没有该对象的引用(数组实体在堆),但第一次gc时,虽然 _64M 对象已经无效了,但他的引用还在局部变量表中,所以此时gc回收不掉。之后随便声明了一个变量顶掉了局部变量表中 _64M的引用,再回收就可以了。
这种方式在一些开源代码中也有看到(好像是netty),当一个对象的生命周期已经结束,且方法内还有一些耗时操作时(栈帧不能释放),将对象引用赋值null,可以达到释放实际对象的目的。但实际上,JIT编译会擦除赋值null的操作,不过JIT后的代码能正确回收这种情况下的内存。所以这种赋值null的行为,在调用次数没有达到JIT标准的方法里,会有奇效。
参考:https://blog.csdn.net/kevin_luan/article/details/22986081
上一篇: 如何删除未推送的git commit?
下一篇: python批量修改word文档内容