JVM垃圾回收演算法
1 什麼時候回收垃圾?
1、什麼場景下該使用什麼垃圾回收策略?
在對記憶體要求苛刻的場景:想辦法提高對象的回收效率,多回收掉一些對象,騰出更多記憶體。
在CPU使用率高的情況下:降低高並發時垃圾回收的頻率,讓CPU更多的去執行你的業務而不是垃圾回收。
2、垃圾回收發生在哪些區域?
堆(回收對象)、方法區(不用的常量、類)
3、對象什麼時候被回收?
引用計數法:通過對象的引用計數器來判斷對象是否被引用。無法處理循環引用問題。
A --> B --> C --> D --> B 此時B被引用了兩次
當A不再引用B
A B --> C --> D --> B BCD循環引用無法被回收。
可達性分析法:以根對象(GC Roots)為起點向下搜索,形成引用鏈。如果對象不在引用鏈上,便視作對象不可達,可以回收。
哪些對象可以作為GC Roots對象?
- 虛擬機棧(棧幀中的本地變數表)中引用的對象
- 方法區中類靜態屬性引用的對象
- 方法區中常量引用的對象
- 本地方法棧中JNI(即 Native方法)引用的對象
注意的是,一個對象不可達,也不一定會被回收。
public class GCTest {
private static GCTest obj;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize方法被調用了");
obj = this; // 重新引用對象,從FQ隊列中出隊
}
public static void main(String[] args) throws InterruptedException {
obj = new GCTest();
obj = null;
System.gc();
TimeUnit.MILLISECONDS.sleep(1000);
extracted();
TimeUnit.MILLISECONDS.sleep(1000);
obj = null; // finalize()方法已被調用過,對象會被直接回收
System.gc();
extracted();
}
private static void extracted() {
if (obj == null) {
System.out.println("obj == null");
} else {
System.out.println("obj可用");
}
}
}
finalize方法被調用了
obj可用
obj == null
finalize()的建議
-
避免使用finalize()方法,操作不當可能會導致問題。上面的例子中,第二次回收時如果不設置obj==null,對象很難被回收掉。
-
finalize()優先順序低,何時會被調用無法確定,因為什麼時間發生GC不確定。
-
建議使用try…catch…finally來替代finalize()。
4、四種引用
強引用,例如new對象,永遠不會回收對象。
軟引用,描述有用但是非必需對象,只有記憶體不足時被回收。
弱引用,描述非必需對象,無論記憶體情況都會被回收。
虛引用,主要用來跟蹤對象被垃圾回收器回收的活動,結合引用隊列使用。不影響對象的生命周期。
Object obj = new Object();
SoftReference<String> sr = new SoftReference<>("helllo");
WeakReference<String> sr = new WeakReference<>("helllo");
ReferenceQueue< String> queue = new ReferenceQueue<>();
PhantomReference< String> pr= new PhantomReference<>(「hello「queue)
2 垃圾回收演算法
垃圾回收演算法
基礎垃圾回收演算法
-
標記清除法(Mark-Sweep):標記後直接刪除。
- 容易產生記憶體碎片;分配大記憶體時速度受到影響,需要找到適合它大小的空間。
-
標記整理法(Mark-Compact):標記,記憶體整理到一側,刪除三步。
- 整理存在開銷,沒有碎片。
-
複製(Copy):記憶體分為兩塊,每次只使用其中一塊;將存活對象複製到另一塊記憶體,然後清空當前記憶體;交換使用另一塊記憶體。
- 記憶體利用率低,性能好,沒有碎片。
綜合垃圾回收演算法
- 分代回收演算法:把記憶體分為多個區域,不同區域使用不同的回收演算法。
- 增量演算法:每次只收集一小塊記憶體區域的垃圾。
分代收集演算法
商業虛擬機普遍採用分代收集演算法,根據對象的存活周期,把記憶體分為多個區域,不同區域使用不同的回收演算法。
分代的好處是更有效的清除不再需要的對象,提升垃圾回收的效率。
- 新生代回收(Minor GC | Young GC)
- 老年代回收(Major GC),通常伴隨Full GC,Major GC≈ Full GC
- 清理整個堆(Full GC)
典型的對象分配流程如下。
新生代採用的是複製演算法,存放存活周期短的對象。老年代對象存活時間長,採用標記-清除或標記-整理演算法。
堆的年輕代和老年代,記憶體比例1:2。年輕代分為Eden和Survivor,記憶體比例8:1:1。
1. 對象首先存放到Eden中,當Eden記憶體滿後,垃圾收集執行緒執行minor gc,通過GC Root進行可達性分析,找到非垃圾對象,複製到Survivor S0區(對象頭中分代年齡標記為1),清空Eden區域,Eden又可以存儲新對象。
2. 當Eden再次滿後,同時對Eden和S0執行minor gc,將非垃圾對象複製到S1(分代年齡加1),清空Eden和S0。S0和S1,同一時間只有一個被使用。
3. 當分代年齡達到15後,對象會被複制到老年代。
4. 當老年代滿了,會對年輕代和老年代執行full GC。full GC不起作用,則會OOM。
命令行輸入jvisualvm
,打開Java VisualVM
工具的Visual GC插件
可以看到程式執行過程中堆容量變化。每次Eden滿進行GC,調整堆區域。
非典型的情況:
- 新建的對象不一定分配到伊甸園。
-
對象大於
-XX:PretenureSizeThreshold
,就會直接分配到老年代 -
新生代空間不夠。例如大數組。
- 對象也不一定要達到年齡才進入老年代
動態年齡:如果Survivor空間中所有相同年齡對象大小的總和大於Survivor空間的一半,那麼年齡大於等於該年齡的對象就可以直接進入老年代。
觸發垃圾回收的條件
Minor GC觸發條件:伊甸園空間不足
Full GC觸發條件:
- 老年代空間不足(老年代已滿;空間碎片太多,沒有連續記憶體分配對象)
- 元空間不足
- 要轉成老年代對象所需的空間大於老年代剩餘空間。
- 顯示調用System.gc(),建議垃圾回收器執行垃圾回收。設置
-XX:+DisableExplicitGC
參數忽略該操作。
分代收集調優思路
- 讓GC盡量發生在新生代,盡量減少Full GC的發生。
- 合理設置Survior區域的大小,避免記憶體浪費。(複製演算法)