1. 程式人生 > >好好理解一下G1收集器

好好理解一下G1收集器

G1收集器是Java1.7新提供的收集器,G1收集器的開發意在在未來的一天,可以取代CMS。但是到目前為止,(2017.08)Java的預設收集器依然是CMS那一套,當然目前很多訊息是在Java9上,G1會成為JVM的預設收集器
G1收集器要解決的核心問題,是讓STW可預測,為了實現這個目標,採取了和傳統方式區別開來的地方:
1.G1的記憶體模型略有變化,將一塊記憶體模型分成多個Region管理,其中,每塊Region大小是在JVM啟動的時候就固定的,使用引數-XX:G1HeapRegionSize設定,範圍為1M~32M
這裡寫圖片描述
上圖可知,G1同樣會劃分Eden Survice 和Old,但不同的是G1的分代只是邏輯上劃分,在實際記憶體中完全是可以不連續的。
有一些Region標明瞭H,它代表Humongous Object(簡稱H-Obj),這表示這些Region儲存的是巨大物件,即大小大於等於region一半的物件。H-obj直接分配到了old ,防止了反覆拷貝移動。 對於H-obj的GC,無疑是很費時間的,G1的G1HeapRegionSize引數目的就是為了讓開發者將Region塊大小調節到一個最適合自己的數值,從而減少H-obj

將堆記憶體分塊管理似乎是可以讓堆記憶體的管理,分配,收集以塊為單位進行,但實際上覆雜的多。Region和Region之間是有關聯的,某一個Region中的物件,很有可能引用著另一個Region中的物件,因此,在GC的標記階段,還是需要掃描整個堆才可以?
對於這個問題,G1採取了空間換時間的方式:
G1在記憶體中維護了一個Remembered Set(簡稱RSet),每個塊都有自己的RSet,裡面記錄的就是當前Region對其他Region的引用資訊,在GC的時候,標記階段同時會檢查RSet是否引用了其他Region的物件,如果有,就去其他的Region繼續標記,如果沒有,標記會停止。
同時注意到,RSet是需要額外記憶體的,並且Region越多,記憶體的額外開銷就越大,因此,在G1收集器下,設定合理的G1HeapRegionSize很重要

2.可預測停頓模型
聽著就非常的高階,然而實際上確實也很高階。G1收集器有一個引數-XX:MaxGCPauseMillis,功能是可以設定G1收集過程停頓時間,預設值是200ms,就是說,你這一個引數配了下去,G1可以在你規定的時間內完成收集?這麼吊?那我設定0是不是就不會STW了?
首先,這個值只是一個期望,G1表示他會盡力達到這個停頓時間,實際上,這個值設的太小也是會有問題的,先理解下這個高階的可預測停頓模型的原理就知道問題在哪了
同一個程式,在不考慮GC自身,在程式碼執行時產生的垃圾不隨GC收集器的變化而變化,也就是垃圾的數量是一定的,具體怎麼收集,何時收集,要看GC的實現,而G1即是,為了滿足開發者配置的MaxGCPauseMillis,而對垃圾進行取捨的收集,也就是說,如果MaxGCPauseMillis配置的過低,每次GC都只能收集G1認為耗時較小的垃圾,而放棄收集其他耗時較長的垃圾,如此堆積下去,會造成更多的次數的GC,或是G1無視了MaxGCPauseMillis耗時更久,甚至當G1表示妥協,用Serial Old收集器去收集,造成更長時間的STW
也就是說,G1收集器實現可預測的停頓,基本思想是通過給垃圾標記優先順序,來進行最優化的收集,那麼如何實現的呢?
除了RSet之外,G1還維護了一個列表,Collcetion Set(簡稱CSet),使用CSet來儲存要收集的垃圾,並標識了優先順序,而標識優先順序使用的是衰減標準偏差為理論基礎實現的(演算法以後再研究吧,總之這是一個可以根據經驗來判斷收集耗時的演算法)。
下圖為G1收集大致流程圖
這裡寫圖片描述


初始標記即在只標記GC Root直接對應的節點,此時需要STW
併發標記和使用者執行緒並行執行,並遍歷初始標記的節點,並且記錄這段執行期間的RSet的變更
最終標記會將RSet變更合併到RSet中,此時需要STW,並取得所有要收集的垃圾
篩選回收階段首先對各個Region的回收價值和成本進行排序(維護CSet),根據使用者所期望的GC停頓時間來制定回收計劃,此時需要STW。

G1提供的GC機制
Young GC:選定所有新生代裡的Region。通過控制新生代的region個數,來控制young GC的時間開銷。
Mixed GC:選定所有新生代裡的Region,外加根據統計得出收集收益高的若干老年代Region。在使用者指定的開銷目標範圍內儘可能選擇收益高的老年代Region。
當G1對於過大增長的記憶體無能為力的時候,G1適用serial GC來收集整個堆。

另外附加幾個G1收集器可用的引數:
-XX:G1HeapRegionSize=n 設定Region大小
-XX:MaxGCPauseMillis 設定G1收集過程停頓時間,預設值200ms,非硬性條件
-XX:G1NewSizePercent 新生代最小值,預設值5%
-XX:G1MaxNewSizePercent 新生代最大值,預設值60%
-XX:ParallelGCThreads StopTheWorld期間,並行GC執行緒數
-XX:ConcGCThreads=n 併發標記階段,並行執行的執行緒數
-XX:InitiatingHeapOccupancyPercent 設定觸發標記週期的 Java 堆佔用率閾值。預設值是45%。這裡的java堆佔比指的是非新生代可用位元組,包括H-obj