GC IN Java

CMS唯一組合方式

  • young:ParNew
  • old:CMS(失敗時降爲Serial)

啓動Parallel GC(parallel scavenge garbage collector)時默認啓動ParallelOldGC,反之亦然。也可以顯示禁用某一個,此時替換爲Serial

  • -XX:+UseParallelGC -XX:-UseParallelOldGC

年輕代採用的都是複製算法,都會STW。

ParallelScavenge和ParNew

ParallelScavenge和ParNew都是並行GC,主要是並行收集young gen,目的和性能其實都差不多。最明顯的區別有下面幾點:
1、PS以前是廣度優先順序來遍歷對象圖的,JDK6的時候改爲默認用深度優先順序遍歷,並留有一個UseDepthFirstScavengeOrder參數來選擇是用深度還是廣度優先。在JDK6u18之後這個參數被去掉,PS變爲只用深度優先遍歷。ParNew則是一直都只用廣度優先順序來遍歷
2、PS完整實現了adaptive size policy,而ParNew及分代式GC框架內的其它GC都沒有實現完(倒不是不能實現,就是麻煩+沒人力資源去做)。所以千萬千萬別再用ParNew+CMS的組合下用UseAdaptiveSizePolicy,請只在使用UseParallelGC或UseParallelOldGC的時候用它
3、由於在分代式GC框架內,ParNew可以跟CMS搭配使用,而ParallelScavenge不能。當時ParNew GC被從Exact VM移植到HotSpot VM的最大原因就是爲了跟CMS搭配使用
4、在PS成爲主要的throughput GC之後,它還實現了針對NUMA的優化;而ParNew一直沒有得到NUMA優化的實現。

G1 GC

G1的collector一側其實就是兩個大部分:

  • 全局併發標記(global concurrent marking)
  • 拷貝存活對象(evacuation)
    而這兩部分可以相對獨立的執行。

Global concurrent marking基於SATB形式的併發標記。它具體分爲下面幾個階段:
1、初始標記(initial marking):暫停階段。掃描根集合,標記所有從根集合可直接到達的對象並將它們的字段壓入掃描棧(marking stack)中等到後續掃描。G1使用外部的bitmap來記錄mark信息,而不使用對象頭的mark word裏的mark bit。在分代式G1模式中,初始標記階段借用young GC的暫停,因而沒有額外的、單獨的暫停階段。
2、併發標記(concurrent marking):併發階段。不斷從掃描棧取出引用遞歸掃描整個堆裏的對象圖。每掃描到一個對象就會對其標記,並將其字段壓入掃描棧。重複掃描過程直到掃描棧清空。過程中還會掃描SATB write barrier所記錄下的引用。
3、最終標記(final marking,在實現中也叫remarking):暫停階段。在完成併發標記後,每個Java線程還會有一些剩下的SATB write barrier記錄的引用尚未處理。這個階段就負責把剩下的引用處理完。同時這個階段也進行弱引用處理(reference processing)。
注意這個暫停與CMS的remark有一個本質上的區別,那就是這個暫停只需要掃描SATB buffer,而CMS的remark需要重新掃描mod-union table裏的dirty card外加整個根集合,而此時整個young gen(不管對象死活)都會被當作根集合的一部分,因而CMS remark有可能會非常慢。
4、清理(cleanup):暫停階段。清點和重置標記狀態。這個階段有點像mark-sweep中的sweep階段,不過不是在堆上sweep實際對象,而是在marking bitmap裏統計每個region被標記爲活的對象有多少。這個階段如果發現完全沒有活對象的region就會將其整體回收到可分配region列表中。

Evacuation階段是全暫停的。它負責把一部分region裏的活對象拷貝到空region裏去,然後回收原本的region的空間。
Evacuation階段可以自由選擇任意多個region來獨立收集構成收集集合(collection set,簡稱CSet),靠per-region remembered set(簡稱RSet)實現。這是regional garbage collector的特徵。

分代式G1模式下有兩種選定CSet的子模式,分別對應young GC與mixed GC:

  • Young GC:選定所有young gen裏的region。通過控制young gen的region個數來控制young GC的開銷。
  • Mixed GC:選定所有young gen裏的region,外加根據global concurrent marking統計得出收集收益高的若干old gen region。在用戶指定的開銷目標範圍內儘可能選擇收益高的old gen region。
    可以看到young gen region總是在CSet內。因此分代式G1不維護從young gen region出發的引用涉及的RSet更新。

分代式G1的正常工作流程就是在young GC與mixed GC之間視情況切換,背後定期做做全局併發標記。Initial marking默認搭在young GC上執行;當全局併發標記正在工作時,G1不會選擇做mixed GC,反之如果有mixed GC正在進行中G1也不會啓動initial marking。
在正常工作流程中沒有full GC的概念,old gen的收集全靠mixed GC來完成。

可以看到在這麼多步驟裏,G1只有兩件事是併發執行的:(1) 全局併發標記;(2) logging write barrier的部分處理。而“拷貝對象”(evacuation)這個很耗時的動作卻不是併發而是完全暫停的。那G1爲何還可以叫做低延遲的GC實現呢?

重點就在於G1雖然會mark整個堆,但並不evacuate所有有活對象的region;通過只選擇收益高的少量region來evacuate,這種暫停的開銷就可以(在一定範圍內)可控。每次evacuate的暫停時間應該跟一般GC的young GC類似。所以G1把自己標榜爲“軟實時”(soft real-time)的GC。

但是畢竟要暫停來拷貝對象,這個暫停時間再怎麼低也有限。G1的evacuation pause在幾十到一百甚至兩百毫秒都很正常。所以切記不要把 -XX:MaxGCPauseMillis 設得太低,不然G1跟不上目標就容易導致垃圾堆積,反而更容易引發full GC而降低性能。通常設到100ms、250ms之類的都可能是合理的。設到50ms就不太靠譜,G1可能一開始還跟得上,跑的時間一長就開始亂來了。

CMS GC

CMS(Concurrent Mark-Sweep)是以犧牲吞吐量爲代價來獲得最短回收停頓時間的垃圾回收器。

  • 初始標記(STW initial mark)
  • 併發標記(Concurrent marking)
  • 併發預清理(Concurrent precleaning)
  • 重新標記(STW remark)
  • 併發清理(Concurrent sweeping)
    併發重置(Concurrent reset)

初始標記 :在這個階段,需要虛擬機停頓正在執行的任務,官方的叫法STW(Stop The Word)。這個過程從垃圾回收的"根對象"開始,只掃描到能夠和"根對象"直接關聯的對象,並作標記。所以這個過程雖然暫停了整個JVM,但是很快就完成了。

併發標記 :這個階段緊隨初始標記階段,在初始標記的基礎上繼續向下追溯標記。併發標記階段,應用程序的線程和併發標記的線程併發執行,所以用戶不會感受到停頓。

併發預清理 :併發預清理階段仍然是併發的。在這個階段,虛擬機查找在執行併發標記階段新進入老年代的對象(可能會有一些對象從新生代晉升到老年代, 或者有一些對象被分配到老年代)。通過重新掃描,減少下一個階段"重新標記"的工作,因爲下一個階段會Stop The World。

重新標記 :這個階段會暫停虛擬機,收集器線程掃描在CMS堆中剩餘的對象。掃描從"跟對象"開始向下追溯,並處理對象關聯。

併發清理 :清理垃圾對象,這個階段收集器線程和應用程序線程併發執行。

併發重置 :這個階段,重置CMS收集器的數據結構,等待下一次垃圾回收。

參考鏈接:https://hllvm-group.iteye.com/group/topic/44381#post-272188

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