《JVM G1源碼分析和調優》讀書筆記


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

步驟:

  1. 選擇CSet
  2. 根處理
  3. Rset處理
  4. 復制
  5. Redirty 重構Rset
  6. 釋放空間

相關日志

可以用-XX:G1LogLevel=finest 打開更詳細的日志
關鍵字 GC pause (G1 Evacuation Pause) (young), 0.0182341 secs...

參數調優

混合回收

混合回收分成兩個階段:

  • 並發標記階段
  • 垃圾回收階段(與新生代回收一致)

並發標記階段又分四個步驟:

  1. 初始標記子階段(initital-mark)
  2. 並發標記子階段(concurrent-mark)
  3. 再標記子階段(remark,STW)
  4. 清理子階段(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
串行回收采用標記清除算法,步驟:

  1. 標記活躍對象
  2. 計算新對象地址
  3. 把所有對象都更新到新地址上
  4. 移動對象完成壓縮

相關日志

[Full GC (Allocation Failure) ..... 0.2036229 secs]....

G1調優

主要涉及的指標有: 吞吐量最大、停段時間盡量端、GC頻率盡量低和堆空間的有效利用率高。
主要調優參數 參見P244 表11-2,主要涉及堆、RSet、標記和GC四個方面的參數。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM