GC的相關算法與JVM的垃圾收集器
GC的相關算法
- 分代管理
- 復制算法
- 標記清除
- 標記壓縮
JVM垃圾收集器
P242 表11-1 不同類型垃圾回收期比較
- 串行收集器 Serial。 Serial GC用於新生代,用了復制算法;Serial Old GC作用於老年代,用的是標記-壓縮算法。STW
- 並行收集器 Parallel。Parallel new作用於新生代,使用了復制算法;Parallel old作用於老年代,用了標記-壓縮算法。STW。並發收集是准確收集,不會產生浮動垃圾。
- 並發收集器 Concurrent-Mark-Sweep。老年代垃圾回收器。使用了標記-清除算法。分為初始標記(Initial-Mark,STW)、並發標記(Concurrent-Mark)、再次標記(Remark,STW)、並發清除(Concurrent-Sweep)。CMS因為需要存儲代際的引用關系,所以有額外的存儲空間的消耗。CMS不是准確收集,會產生浮動垃圾。
- 垃圾優先收集器 G1。按照分區進行收集,新生代的分區總是會回收,老生代則是並發標記后選擇部分回收效果最好的分區。G1分為三種回收方式:
新生代回收
young、混合回收
(mixed,既收集新生代也收集部分老年代)、FUll GC
。新生代回收
僅僅在開始前需要STW。混合回收
分成兩個階段:並發標記階段與垃圾回收階段。並發標記階段又分四個步驟:初始標記子階段(initital-mark)、並發標記子階段(concurrent-mark)、再標記子階段(remark,STW)、清理子階段(cleanup,STW)。因分區設計,G1引用關系的存儲占用額外空間的消耗較大。G1不是准確收集,會產生浮動垃圾。
G1基本概念
分區
G1是將內存分成一個個小區域使用。這些區域稱之為Heap Region。擴展的有YHR
(新生代分區),OHR
(老生代分區),HHR
(大對象分區)等等
為了達到分配效率與清理效率的平衡,HR的大小有上下限值,即1MB-32MB。結合整個堆空間分為2048個HR,那么通常G1管理的最大的堆是32MB*2048=64G。
新生代大小
用參數設置使得G1能推斷出最大值與最小值
涉及的參數有:
- 新生代最大值MaxNewSize、最小值NewSize、Xmn(等價於MaxNewSize和NewSize,且MaxNewSize=NewSize)
- NewRatio,如果上條參數設置了,則忽略本參數
- 如果僅僅設置了NewRatio,則新生代最大值與最小值相同:整個堆空間/(newRatio+1)
- 如果沒有設置最大值和最小值,或者只設置了其中一個,那么G1將根據參數G1MaxNewSizePercent(默認60)和G1NewSizePercent(默認是5)占整個堆空間的比例來計算。
如果G1推斷出的新生代的最大值與最小值相等,則說明新生代不會動態變化,不會動態變化則可能導致后續新生代GC時不能滿足期望的停頓時間,所以有文章提到G1不建議設置Xmn參數。
相關代碼在 share/vm/gc_implementation/g1/g1collectorPolicy.cpp
G1啟發式推斷新生代大小
G1有一個線程專門抽樣處理預測新生代列表的長度應該多大,並動態調整。
何時擴展以及一次擴展多少內存?
參數-XX:GCTimeRatio 表示GC與應用的耗費時間的比,G1默認是9。也就是說GC的耗時與應用耗時占比超過10%時,進行動態擴展。擴展大小的參數是G1ExpandByPercentOfAvailable,同時至少大於1MB,至多不能超過當前已經分配的大小的一倍。
代碼在 size_t G1CollectorPolicy::expansion_amount()....
該書在第五章講refine線程時對此點有更詳細的闡述
G1停頓預測模型
比較偏數學 我就很快跳過去了
G1的預測邏輯是基於衰減平均(Decaying Average)和衰減標准差。
卡表和位圖
卡表(CardTable)是CMS中中常見概念之一。我理解成分區間對象引用關系的描述 的存放處或者說存放的數據結構。此書也是講的較為簡略,細節可以參見《垃圾回收算法手冊:自動內存管理的藝術》
對象頭
講JVM內存模型必講對象頭。可以參見我寫的JVM中對象模型及相應名詞概念
棧幀、線程等
棧幀可以參見 封亞飛 寫的《揭秘Java虛擬機》第七章 Java棧幀,寫的更詳細。此書只是一筆帶過,看了跟沒看沒啥差別。
G1的對象分配
- 快速分配與慢速分配
- 快速分配通過TLAB(Thread Local Allocation Buffer)實現。TLAB自己的分配是CAS操作。TLAB內部給對象分配是無鎖的,因為只有自己線程用嘛。
- TLAB機制或產生內存浪費,因為一個對象不會分配在兩個TLAB區域,所以TLAB最末端的尾巴區域可能會殘留空着。可以通過TLABRefillWasteFraction參數調整,表示允許產生浪費的比例。默認值是64,即表示1/64空間可以浪費。
- TLAB大小可以自動調整,但是上限不會超過HR的一半。、
- 可以使用參數-XX:-ResizeTLAB禁用ResizeTLAB,並使用參數-XX:TLABSize指定一個大小。-XX:+PrintTLAB可以跟蹤TLAB工作情況。
- 一般不建議修改TLAB參數,建議使用默認值
TLAB快速分配的代碼在 HeapWord* CollectedHeap::allocate_from_tlab...
G1的Refine線程
先講Rset
Rset
Rset是干什么用的?
- Rset是一種抽象概念,記錄了在不同代際之間的引用關系,目的是為了加速GC。
- 通俗地說,可以用Rset記錄從非收集部分指向收集部分的指針集合。對於這種記錄述求,有兩種方式,一是
我引用了誰
,稱為Point Out
;一是誰引用了我
,稱為Point In
。G1采用后者。 - G1中需要記錄代際之間的引用關系包括:老生代分區到新生代分區之間的引用關系(YGC時,這個引用關系是GC Roots的一部分,老生代引用過來的不能被回收掉嘛...);老生代分區到老生代分區之間的引用關系(混合GC時用)
- Rset與卡表的關系參見P68圖4-1
- G1引入了PRT,TODO:沒看太懂
- DCQ與Refinemnet zone的四色區域沒看太懂
Rset寫屏障
為啥談到寫屏障,因為Refine是線程關注的是應用關系的變更,但是他是如何識別引用關系的變更的呢?就是靠寫屏障完成。下面講寫屏障相關要點:
- 寫屏障這個詞我不知道為啥這樣命名,直覺上不好理解。
- 我對其的理解就是,寫操作前后的攔截器處理。比如我對字段賦值putfield,在賦值前我要告訴DCQ這個對象被我引用了,這就是寫屏障動作。
- 書上的說法:
寫屏障
是指在改變特定內存的值時,額外執行的一些動作。 - CMS是通過寫屏障記錄引用刮不洗,G1也是。
- 寫屏障會有優化,不是所有的引用關系變更都會被記錄。
- 不記錄新生代到新生代的引用,或者新生代到老生代的引用,在寫屏障時過濾
- 過濾掉同一個分區內部引用,在Rset處理時過濾
- 過濾掉空引用,在Rset處理時過濾
Refine線程
- Refine線程是一組,是一個線程池,不是一個。
- 我對他的理解是,一個線程用於抽樣,主要作用設置新生代分區的個數。其余線程用於管理Rset,Rset的更新不是同步完成的,是靠Refine線程異步完成的,異步又是靠DCQ dirty card queue隊列暫存過渡的。
- Refine涉及的JVM比較復雜,未細細研究
- 相關參數:可以通過-XX:+G1TraceConcRefinement觀察Refine線程工作情況。通過-XX:+G1SummarizeRSetStats觀察Rset更新。
新生代回收
上面已經講了,G1 GC分三種: 新生代回收
young、混合回收
(mixed,既收集新生代也收集部分老年代)、FUll GC
步驟:
- 選擇CSet
- 根處理
- Rset處理
- 復制
- Redirty 重構Rset
- 釋放空間
相關日志
可以用-XX:G1LogLevel=finest 打開更詳細的日志
關鍵字 GC pause (G1 Evacuation Pause) (young), 0.0182341 secs...
參數調優
混合回收
混合回收
分成兩個階段:
- 並發標記階段
- 垃圾回收階段(與新生代回收一致)
並發標記階段又分四個步驟:
- 初始標記子階段(initital-mark)
- 並發標記子階段(concurrent-mark)
- 再標記子階段(remark,STW)
- 清理子階段(cleanup,STW
並發標記的難點:
正在標記過程中的對象引用關系發生了改變。
通過三色標記法與STAB算法結合寫屏障完成。
寫屏障代碼在 oop_store中,oop.inline.hpp
相關日志
關鍵字 GC pause (G1 Evacuation Pause) (mixed), 0.0106341 secs...
GC pause (G1 Evacuation Pause) (young) (initial-mark),.... // 初始標記借用了YGC
[GC concurrent-mark-start]...
[GC concurrent-mark-end]...
[GC remark ...]...
[GC cleanup ...]...
參數調優
Full GC
JDK10之前FGC是串行的,JDK10之后支持並行。
代碼在 G1CollectedHeap::do_collection
串行回收采用標記清除算法,步驟:
- 標記活躍對象
- 計算新對象地址
- 把所有對象都更新到新地址上
- 移動對象完成壓縮
相關日志
[Full GC (Allocation Failure) ..... 0.2036229 secs]....
G1調優
主要涉及的指標有: 吞吐量最大、停段時間盡量端、GC頻率盡量低和堆空間的有效利用率高。
主要調優參數 參見P244 表11-2,主要涉及堆、RSet、標記和GC四個方面的參數。