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高級特性與最佳實踐(第二版)》第八章