欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JVM局部变量表

程序员文章站 2022-04-18 18:47:50
...

    局部变量表是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