[Java] Serial 、Parallel、CMS、G1 GC JVM

這裡大概說明4個GC ,如果進行了錯誤的選擇將會大大的影響程式的效能。

Serial 、Parallel、CMS、G1

Reference:
https://go.cloudbees.com/docs/solutions/jvm-troubleshooting/

Serial收集器

    Serial收集器是JAVA虛擬機器中最基本、歷史最悠久的收集器,在JDK 1.3.1之前是JAVA虛擬機器新生代收集的唯一選擇。Serial收集器是一個單執行緒的收集器,但它的“單執行緒”的意義並不僅僅是說明它只會使用一個CPU或一條收集執行緒去完成垃圾收集工作,更重要的是在它進行垃圾收集時,必須暫停其他所有的工作執行緒,直到它收集結束。

     Serial收集器到JDK1.7為止,它依然是JAVA虛擬機器執行在Client模式下的預設新生代收集器。它也有著優於其他收集器的地方:簡單而高效(與其他收集器的單執行緒比),對於限定單個CPU的環境來說,Serial收集器由於沒有執行緒互動的開銷,專心做垃圾收集自然可以獲得最高的單執行緒收集效率。在使用者的桌面應用場景中,分配給虛擬機器管理的記憶體一般來說不會很大,收集幾十兆甚至一兩百兆的新生代(僅僅是新生代使用的記憶體,桌面應用基本上不會再大了),停頓時間完全可以控制在幾十毫秒最多一百多毫秒以內,只要不是頻繁發生,這點停頓是可以接受的。所以,Serial收集器對於執行在Client模式下的虛擬機器來說是一個很好的選擇。

 Parallel(並行)收集器

    這是 JVM 的預設收集器。就像它的名字,其最大的優點是使用多個執行緒來通過掃描並壓縮堆。序列收集器在GC時會停止其他所有工作執行緒(stop-the-world),CPU利用率是最高的,所以適用於要求高吞吐量(throughput)的應用,但停頓時間(pause time)會比較長,所以對web應用來說就不適合,因為這意味著使用者等待時間會加長。而並行收集器可以理解是多執行緒序列收集,在序列收集基礎上採用多執行緒方式進行GC,很好的彌補了序列收集的不足,可以大幅縮短停頓時間(如下圖表示的停頓時長高度,併發比並行要短),因此對於空間不大的區域(如young generation),採用並行收集器停頓時間很短,回收效率高,適合高頻率執行。

CMS收集器

    CMS(Concurrent Mark Sweep)收集器是基於“標記-清除”演算法實現的,它使用多執行緒的演算法去掃描堆(標記)並對發現的未使用的物件進行回收(清除)。整個過程分為6個步驟,包括:

  1. 初始標記(CMS initial mark)
  2. 併發標記(CMS concurrent mark)
  3. 併發預清理(CMS-concurrent-preclean)
  4. 重新標記(CMS remark)
  5. 併發清除(CMS concurrent sweep)
  6. 併發重置(CMS-concurrent-reset)

    其中初始標記、重新標記這兩個步驟仍然需要“Stop The World”。初始標記僅僅只是標記一下GC Roots能直接關聯到的物件,速度很快,併發標記階段就是進行GC Roots Tracing的過程,而重新標記階段則是為了修正併發標記期間,因使用者程式繼續運作而導致標記產生變動的那一部分物件的標記記錄,這個階段的停頓時間一般會比初始標記階段稍長一些,但遠比並發標記的時間短。其他動作都是併發的。

    需要注意的是,CMS收集器無法處理浮動垃圾(Floating Garbage),可能出現“Concurrent Mode Failure”失敗而導致另一次Full GC的產生。由於CMS併發清理階段使用者執行緒還在執行著,伴隨程式的執行自然還會有新的垃圾不斷產生,這一部分垃圾出現在標記過程之後,CMS無法在本次收集中處理掉它們,只好留待下一次GC時再將其清理掉。這一部分垃圾就稱為“浮動垃圾”。也是由於在垃圾收集階段使用者執行緒還需要執行,即還需要預留足夠的記憶體空間給使用者執行緒使用,因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進行收集,需要預留一部分空間提供併發收集時的程式運作使用。在預設設定下,CMS收集器在老年代使用了68%的空間後就會被啟用,這是一個偏保守的設定,如果在應用中老年代增長不是太快,可以適當調高參數-XX:CMSInitiatingOccupancyFraction的值來提高觸發百分比,以便降低記憶體回收次數以獲取更好的效能。要是CMS執行期間預留的記憶體無法滿足程式需要,就會出現一次“Concurrent Mode Failure”失敗,這時候虛擬機器將啟動後備預案:臨時啟用Serial Old收集器來重新進行老年代的垃圾收集,這樣停頓時間就很長了。所以說引數-XX:CMSInitiatingOccupancyFraction設定得太高將會很容易導致大量“Concurrent Mode Failure”失敗,效能反而降低。

   還有一個缺點,CMS是一款基於“標記-清除”演算法實現的收集器,這意味著收集結束時會產生大量空間碎片。空間碎片過多時,將會給大物件分配帶來很大的麻煩,往往會出現老年代還有很大的空間剩餘,但是無法找到足夠大的連續空間來分配當前物件,不得不提前觸發一次Full GC。為了解決這個問題,CMS收集器提供了一個-XX:+UseCMSCompactAtFullCollection開關引數,用於在“享受”完Full GC服務之後額外免費附送一個碎片整理過程,記憶體整理的過程是無法併發的。空間碎片問題沒有了,但停頓時間不得不變長了。虛擬機器設計者們還提供了另外一個引數-XX: CMSFullGCsBeforeCompaction,這個引數用於設定在執行多少次不壓縮的Full GC後,跟著來一次帶壓縮的。

    該演算法與並行收集器的另一個缺點是吞吐量的它使用更多的 CPU,為了使應用程式提供更好的體驗,通過使用多個執行緒來執行掃描和收集。這種情況長時間的執行會使應用程式停頓下來,可以使用提高空間來換取高效的執行。但是,這種演算法的使用不是預設的。您必須指定 XX: + USeParNewGC來使用它。如果你可以提供更多的CPU資源的話以避免應用程式暫停,那麼你可以使用CMS收集器。假設你的堆的大小小於 4 Gb你必須分配大於 4 GB的資源。

G1收集器

    G1垃圾收集器在JDK7 update 4之後對大於4G的堆有了更好的支援,G1是一個針對多處理器大容量記憶體的伺服器端的垃圾收集器,其目標是在實現高吞吐量的同時,儘可能的滿足垃圾收集暫停時間的要求。G1在執行一些Java堆空間中的全區域操作(如:全域性標記)時是和應用程式執行緒併發進行的,因此減少了Java堆空間的中斷比例。(譯者注:可簡單理解為減少了Stop-the-World的時間比例)。

    它與前面的CMS收集器相比有兩個顯著的改進:一是G1收集器是基於“標記-整理”演算法實現的收集器,也就是說它不會產生空間碎片,這對於長時間執行的應用系統來說非常重要。二是它可以非常精確地控制停頓,既能讓使用者明確指定在一個長度為M毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒,具備了一些實時Java(RTSJ)的垃圾收集器的特徵。

首先將Java堆空間劃分為一些大小相等的區域(region),每個區域都是虛擬機器中的一段連續記憶體空間。G1通過執行併發的全域性標記來確定整個Java堆空間中存活的物件。標記階段完成後,G1就知道哪些區域基本上是空閒的。在回收記憶體時優先回收這些區域,這樣通常都會回收相當數量的記憶體。這就是為什麼它叫做Garbage-First的原因。顧名思義G1關注某些區域的回收和整理,這些區域中的物件很有可能被完全回收。而且G1使用了一個暫停時間預測模型使得暫停時間控制在使用者指定的暫停時間內,並根據使用者指定的暫停時間來選擇合適的區域回收記憶體。

    G1確定了可回收的區域後就是篩選回收(evacuation)階段了。在此階段將物件從一個或多個區域複製到單一區域,同時整理和釋放記憶體。該階段是在多個處理器上多個執行緒並行進行的,因此減少了暫停時間並提高了吞吐量。G1在每一次的垃圾收集過程中都不斷地減少碎片,並能夠將暫停時間控制在一定範圍內。這些已經是以前的垃圾收集器無法完成的了。比如:CMS收集器並不做記憶體整理。ParallelOld收集器只是對整個Java堆空間做整理,這樣導致相當長的暫停時間。

圖片從這邊借來的. 有興趣的人可以自行參考.
https://plumbr.io/handbook/garbage-collection-algorithms-implementations/g1

 

另外這邊列出JVM參數中常用的相關的設定.
更多細節可以參考Oracle官方文件
https://www.oracle.com/technetwork/articles/java/vmoptions-jsp-140102.html

Classification Option Remarks
Serial GC -XX:+UseSerialGC  
Parallel GC -XX:+UseParallelGC
-XX:ParallelGCThreads=value
 
Parallel Compacting GC -XX:+UseParallelOldGC  
CMS GC -XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:+CMSParallelRemarkEnabled
-XX:CMSInitiatingOccupancyFraction=value
-XX:+UseCMSInitiatingOccupancyOnly
‑XX:+CMSConcurrentMTEnabled
 
G1GC -XX:+UseG1GC available from Java 7