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

你可能感兴趣的:(JVM,java)