java 局部變量表中Variable Slot複用帶來的內存回收問題

java 局部變量表中Variable Slot複用帶來的內存回收問題

背景介紹

java虛擬機在執行java程序的過程中會把它所管理的內存劃分爲若干個不同的數據區域
下圖是jdk8後的JVM內存佈局,引用於https://www.cnblogs.com/czwbig/p/11127124.html
在這裏插入圖片描述

從圖中可以看到,棧幀是虛擬機棧中的元素,是一個方法在內存中的實體映射,局部變量表則是存儲方法中的局部變量的一張表。

局部變量表中一般一個Variable Slot存儲一個變量,除了64位的類型變量要用兩個slot。
slot可以複用,方法中的變量的作用域不一定是整個方法,在離開變量作用域後,對應的slot可以給其他變量使用。
slot複用可能會引起內存回收的問題,先上代碼

public class Main {
    public static void main(String[] args) {
        fun1();
        fun2();
        fun3();
    }


    private static void fun1() {
        System.out.println("fun1 gc");
        byte[] placeholder = new byte[64 * 1024 * 1024];
        System.gc();
    }

    private static void fun2() {
        System.out.println("fun2 gc");
        {
            byte[] placeholder = new byte[64 * 1024 * 1024];
        }
        System.gc();
    }

    private static void fun3() {
        System.out.println("fun3 gc");
        {
            byte[] placeholder = new byte[64 * 1024 * 1024];
        }
        int a=0;
        System.gc();
    }
}

執行過程與結果如下

C:\Users\account\IdeaProjects\StackFrameReuseTest\src\main\java>javac Main.java

C:\Users\account\IdeaProjects\StackFrameReuseTest\src\main\java>java -verbose:gc Main
[0.031s][info][gc] Using G1
fun1 gc
[0.221s][info][gc] GC(0) Pause Full (System.gc()) 66M->65M(192M) 2.252ms
fun2 gc
[0.223s][info][gc] GC(1) Pause Young (Concurrent Start) (G1 Humongous Allocation) 66M->65M(192M) 0.349ms
[0.223s][info][gc] GC(2) Concurrent Cycle
[0.278s][info][gc] GC(2) Pause Remark 130M->65M(192M) 0.471ms
[0.282s][info][gc] GC(3) Pause Full (System.gc()) 65M->65M(192M) 1.868ms
[0.282s]fun3 gc[info][gc]
 GC(2) Concurrent Cycle 59.368ms
[0.283s][info][gc] GC(4) Pause Young (Concurrent Start) (G1 Humongous Allocation) 66M->65M(192M) 0.409ms
[0.284s][info][gc] GC(5) Concurrent Cycle
[0.299s][info][gc] GC(5) Pause Remark 130M->65M(192M) 0.502ms
[0.318s][info][gc] GC(6) Pause Full (System.gc()) 65M->0M(10M) 17.656ms
[0.319s][info][gc] GC(5) Concurrent Cycle 34.272ms

代碼解析

在三個函數fun1 fun2 fun3都生成64MB的數據,隨後做內存回收

  • fun1中是最簡單的方式,placeholder的作用域是整個fun1,所以gc後內存沒有被回收([0.221s][info][gc] GC(0) Pause Full (System.gc()) 66M->65M(192M) 2.252ms)

  • fun2中加了個花括號限制placeholder的作用域,在括號外gc,結果發現內存還是沒被回收([0.282s][info][gc] GC(3) Pause Full (System.gc()) 65M->65M(192M) 1.868ms)

原因:雖然已經離開了placeholder的作用域,但方法fun2的局部變量表中某個slot還存在着對placeholder的引用,在gc前沒有任何對局部變量表的讀寫操作,該slot就沒有被複用,此時gc就不會回收placeholder的內存,因爲還存在引用

  • fun3基於fun2在gc前定義了另一個局部變量a,此時placeholder所在的slot會被複用,解除對placeholder的引用,隨後gc即可回收其內存([0.318s][info][gc] GC(6) Pause Full (System.gc()) 65M->0M(10M) 17.656ms)

適用情景

一個方法中前面定義了佔用大量內存,但後面已經不再使用的變量,而方法後面的操作需要大內存或者耗時長,此時使用上述fun3的方法促使變量內存被快速回收,有利於提高方法運行的性能。

參考

《深入理解Java虛擬機:JVM高級特性與最佳實踐(第二版)》第八章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章