天天看點

深入了解 Java 虛拟機【4】HotSpot 垃圾收集器

HotSpot 虛拟機提供了多種垃圾收集器,每種收集器都有各自的特點,雖然我們要對各個收集器進行比較,但并非為了挑選出一個最好的收集器。我們選擇的隻是對具體應用最合适的收集器。

新生代垃圾收集器

Serial 垃圾收集器(單線程)

隻開啟一條 GC 線程進行垃圾回收,并且在垃圾收集過程中停止一切使用者線程(Stop The World)。

一般用戶端應用所需記憶體較小,不會建立太多對象,而且堆記憶體不大,是以垃圾收集器回收時間短,即使在這段時間停止一切使用者線程,也不會感覺明顯示卡頓。是以 Serial 垃圾收集器适合用戶端使用。

由于 Serial 收集器隻使用一條 GC 線程,避免了線程切換的開銷,進而簡單高效。

深入了解 Java 虛拟機【4】HotSpot 垃圾收集器

ParNew 垃圾收集器(多線程)

ParNew 是 Serial 的多線程版本。由多條 GC 線程并行地進行垃圾清理。但清理過程依然需要 Stop The World。

ParNew 追求“低停頓時間”,與 Serial 唯一差別就是使用了多線程進行垃圾收集,在多 CPU 環境下性能比 Serial 會有一定程度的提升;但線程切換需要額外的開銷,是以在單 CPU 環境中表現不如 Serial。

深入了解 Java 虛拟機【4】HotSpot 垃圾收集器

Parallel Scavenge 垃圾收集器(多線程)

Parallel Scavenge 和 ParNew 一樣,都是多線程、新生代垃圾收集器。但是兩者有巨大的不同點:

Parallel Scavenge:追求 CPU 吞吐量,能夠在較短時間内完成指定任務,是以适合沒有互動的背景計算。

ParNew:追求降低使用者停頓時間,适合互動式應用。

吞吐量 = 運作使用者代碼時間 / (運作使用者代碼時間 + 垃圾收集時間)

追求高吞吐量,可以通過減少 GC 執行實際工作的時間,然而,僅僅偶爾運作 GC 意味着每當 GC 運作時将有許多工作要做,因為在此期間積累在堆中的對象數量很高。單個 GC 需要花更多的時間來完成,進而導緻更高的暫停時間。而考慮到低暫停時間,最好頻繁運作 GC 以便更快速完成,反過來又導緻吞吐量下降。

通過參數 -XX:GCTimeRadio 設定垃圾回收時間占總 CPU 時間的百分比。

通過參數 -XX:MaxGCPauseMillis 設定垃圾處理過程最久停頓時間。

通過指令 -XX:+UseAdaptiveSizePolicy 開啟自适應政策。我們隻要設定好堆的大小和 MaxGCPauseMillis 或 GCTimeRadio,收集器會自動調整新生代的大小、Eden 和 Survivor 的比例、對象進入老年代的年齡,以最大程度上接近我們設定的 MaxGCPauseMillis 或 GCTimeRadio。

老年代垃圾收集器

Serial Old 垃圾收集器(單線程)

Serial Old 收集器是 Serial 的老年代版本,都是單線程收集器,隻啟用一條 GC 線程,都适合用戶端應用。它們唯一的差別就是:Serial Old 工作在老年代,使用“标記-整理”算法;Serial 工作在新生代,使用“複制”算法。

Parallel Old 垃圾收集器(多線程)

Parallel Old 收集器是 Parallel Scavenge 的老年代版本,追求 CPU 吞吐量。

CMS 垃圾收集器

CMS(Concurrent Mark Sweep,并發标記清除)收集器是以擷取最短回收停頓時間為目标的收集器(追求低停頓),它在垃圾收集時使得使用者線程和 GC 線程并發執行,是以在垃圾收集過程中使用者也不會感到明顯的卡頓。

初始标記:Stop The World,僅使用一條初始标記線程對所有與 GC Roots 直接關聯的對象進行标記。

并發标記:使用多條标記線程,與使用者線程并發執行。此過程進行可達性分析,标記出所有廢棄對象。速度很慢。

重新标記:Stop The World,使用多條标記線程并發執行,将剛才并發标記過程中新出現的廢棄對象标記出來。

并發清除:隻使用一條 GC 線程,與使用者線程并發執行,清除剛才标記的對象。這個過程非常耗時。

并發标記與并發清除過程耗時最長,且可以與使用者線程一起工作,是以,總體上說,CMS 收集器的記憶體回收過程是與使用者線程一起并發執行的。

深入了解 Java 虛拟機【4】HotSpot 垃圾收集器

CMS 的缺點:

吞吐量低

無法處理浮動垃圾,導緻頻繁 Full GC

使用“标記-清除”算法産生碎片空間

對于産生碎片空間的問題,可以通過開啟 -XX:+UseCMSCompactAtFullCollection,在每次 Full GC 完成後都會進行一次記憶體壓縮整理,将零散在各處的對象整理到一塊。設定參數 -XX:CMSFullGCsBeforeCompaction告訴 CMS,經過了 N 次 Full GC 之後再進行一次記憶體整理。

G1 通用垃圾收集器

G1 是一款面向服務端應用的垃圾收集器,它沒有新生代和老年代的概念,而是将堆劃分為一塊塊獨立的 Region。當要進行垃圾收集時,首先估計每個 Region 中垃圾的數量,每次都從垃圾回收價值最大的 Region 開始回收,是以可以獲得最大的回收效率。

從整體上看, G1 是基于“标記-整理”算法實作的收集器,從局部(兩個 Region 之間)上看是基于“複制”算法實作的,這意味着運作期間不會産生記憶體空間碎片。

這裡抛個問題

一個對象和它内部所引用的對象可能不在同一個 Region 中,那麼當垃圾回收時,是否需要掃描整個堆記憶體才能完整地進行一次可達性分析?

并不!每個 Region 都有一個 Remembered Set,用于記錄本區域中所有對象引用的對象所在的區域,進行可達性分析時,隻要在 GC Roots 中再加上 Remembered Set 即可防止對整個堆記憶體進行周遊。

如果不計算維護 Remembered Set 的操作,G1 收集器的工作過程分為以下幾個步驟:

并發标記:使用一條标記線程與使用者線程并發執行。此過程進行可達性分析,速度很慢。

最終标記:Stop The World,使用多條标記線程并發執行。

篩選回收:回收廢棄對象,此時也要 Stop The World,并使用多條篩選回收線程并發執行。

原文釋出時間為:2018-07-20

本文作者:楊立濱

本文來自雲栖社群合作夥伴“

Java知音

”,了解相關資訊可以關注“