您當前的位置:首頁 > 收藏

JVM G1(Garbage-First Garbage Collector)收集器全過程剖析

作者:由 宗離 發表于 收藏時間:2020-05-03

JVM G1(Garbage-First Garbage Collector)收集器全過程剖析

G1

垃圾收集器的設計原則是“首先收集儘可能多的垃圾(Garbage First)”,目標是為了儘量縮短處理超大堆(超過4GB)產生的停頓。

因此,

G1

並不會等記憶體耗盡(比如

Serial

序列收集器、

Parallel

並行收集器 )者快耗盡(

CMS

)的時候才開始垃圾回收,而是在內部採用了啟發式演算法,在老年代中找出具有高收集收益的分割槽(

Region

)進行收集。

同時

G1

可以根據使用者設定的

STW

(Stop-The-World)停頓時間目標(響應時間優先)自動調整年輕代和總堆的大小,停頓時間越短年輕代空間就可能越小,總堆空間越大。

G1

相對於

CMS

一個比較明顯的優勢是,記憶體碎片的產生率大大降低。

G1

在 JDK7u4以上都可以使用,在JDK9開始,

G1

為預設的垃圾收集器,以替代

CMS

G1演算法

演算法:三色標記 + SATB

G1的特性

面向服務端應用的垃圾收集器

並行與併發:G1能充分利用多CPU、

多核

環境使用多個CPU或CPU核心來縮短

STW

(Stop-The-World)停頓時間。

分代收集:G1物理上不分代,但邏輯上仍然有分代的概念。

空間整合:不會產生記憶體空間碎片,收集後可提供規整的可用記憶體,整理空閒空間更快。

可預測的停頓(它可以有計劃的避免在整個JAVA堆中進行全區域的垃圾收集)

適用於不需要實現很高吞吐量的場景

JAVA堆記憶體佈局與其它收集器存在很大差別,它將整個JAVA堆劃分為多個大小相等的獨立區域或分割槽(

Region

)。

G1收集器中,虛擬機器使用

Remembered Set

來避免全堆掃描。

G1的記憶體模型

JVM G1(Garbage-First Garbage Collector)收集器全過程剖析

分割槽概念

傳統的GC收集器將連續的記憶體空間劃分為新生代、老年代和永久代(JDK 8去除了永久代,引入了元空間Metaspace),

這種劃分的特點是各代的儲存地址(邏輯地址,下同)是連續的。如下圖所示:

而G1的各代儲存地址是不連續的,每一代都使用了n個不連續的大小相同的

Region

,每個

Region

佔有一塊連續的虛擬記憶體地址。

Region

(區域,分割槽)

G1採用了分割槽(

Region

)的思路,將整個堆空間分成若干個大小相等的記憶體區域,每次分配物件空間將逐段地使用記憶體。

雖然還保留了新生代和老年代的概念,但新生代和老年代不再是物理隔離,它們都是一部分

Region

(不需要連續)的集合。

因此,在堆的使用上,G1並不要求物件的儲存一定是物理上連續的,只要邏輯上連續即可;

每個分割槽Region也不會確定地為某個代服務,可以按需在年輕代和老年代之間切換。

啟動時可以透過引數

-XX:G1HeapRegionSize=n

可指定分割槽大小(1MB~32MB,且必須是2的冪),預設將整堆劃分為2048個分割槽。

Card

(卡片)

在每個分割槽

Region

內部又被分成了若干個大小為512 Byte卡片(Card),標識堆記憶體最小可用粒度。

所有分割槽

Region

的卡片將會記錄在全域性卡片表(

Global Card Table

)中。

分配的物件會佔用物理上連續的若干個卡片。

當查詢對分割槽

Region

內物件的引用時便可透過記錄卡片來查詢該引用物件(見

RSet

)。

每次對記憶體的回收,都是對指定分割槽的卡片進行處理。

Heap

(堆)

G1同樣可以透過

-Xms/-Xmx

來指定堆空間大小。

當發生年輕代收集(

YGC

)或混合收集(

Mixed GC

)時,透過計算GC與應用的耗費時間比,自動調整堆空間大小。

如果GC頻率太高,則透過增加堆尺寸,來減少GC頻率,相應地GC佔用的時間也隨之降低;

目標引數

-XX:GCTimeRatio

即為GC與應用的耗費時間比,G1預設為12(JDK7,8為99,JDK11+開始為12),而CMS預設為99,因為CMS的設計原則是耗費在GC上的時間儘可能的少。

另外,當空間不足,如物件空間分配或轉移失敗時,G1會首先嚐試增加堆空間,如果擴容失敗,則發起擔保的

Full GC

Full GC

後,堆尺寸計算結果也會調整堆空間。

分代概念

Generation

(分代 )

分代垃圾收集可以將關注點集中在最近被分配的物件上,而無需整堆掃描,避免長命物件的複製,同時獨立收集有助於降低響應時間。

雖然分割槽使得記憶體分配不再要求緊湊的記憶體空間,但G1依然使用了分代的思想。

與其他垃圾收集器類似,G1將記憶體在邏輯上劃分為年輕代和老年代,其中年輕代又劃分為Eden空間和Survivor空間。

但年輕代空間並不是固定不變的,當現有年輕代分割槽佔滿時,JVM會分配新的空閒分割槽加入到年輕代空間。

整個年輕代記憶體會在初始空間

-XX:NewSize

與最大空間

-XX:MaxNewSize

之間動態變化,且由引數目標暫停時間

-XX:MaxGCPauseMillis

、需要擴縮容的大小以及分割槽的已記憶集合(

RSet

)計算得到。

當然,G1依然可以設定固定的年輕代大小(引數

-XX:NewRatio

-Xmn

),但同時暫停目標將失去意義。

Local allocation buffer

LAB

) (本地分配緩衝) 值得注意的是,由於分割槽的思想,每個執行緒均可以“認領”某個分割槽

Region

用於執行緒本地的記憶體分配,而不需要顧及分割槽是否連續。

因此,每個應用執行緒和GC執行緒都會獨立的使用分割槽,進而減少同步時間,提升GC效率,這個分割槽

Region

稱為本地分配緩衝區(

LAB

)。

應用執行緒本地緩衝區

TLAB

: 應用執行緒可以獨佔一個本地緩衝區(

TLAB

)來建立的物件,而大部分都會落入Eden區域(巨型物件或分配失敗除外),因此TLAB的分割槽屬於Eden空間;

GC執行緒本地緩衝區

GCLAB

: 每次垃圾收集時,每個GC執行緒同樣可以獨佔一個本地緩衝區(

GCLAB

)用來轉移物件,每次回收會將物件複製到Suvivor空間或老年代空間;

晉升本地緩衝區

PLAB

: 對於從Eden/Survivor空間晉升(Promotion)到Survivor/老年代空間的物件,同樣有GC獨佔的本地緩衝區進行操作,該部分稱為晉升本地緩衝區(

PLAB

)。

分割槽模型

Humongous Object

(巨型物件)

一個大小達到甚至超過分割槽

Region

50%以上的物件稱為巨型物件(

Humongous Object

)。 巨型物件會獨佔一個、或多個連續分割槽,其中第一個分割槽被標記為開始巨型(

StartsHumongous

),相鄰連續分割槽被標記為連續巨型(

ContinuesHumongous

)。

Humongous Object

有以下特點:

Humongous Object

直接分配到了 老年代,防止了反覆複製移動。

當執行緒為巨型分配空間時,不能簡單在

TLAB

進行分配,因為巨型物件的移動成本很高,而且有可能一個分割槽不能容納巨型物件。 因此,巨型物件會直接在老年代分配,所佔用的連續空間稱為巨型分割槽(

Humongous Region

)。

Humongous Object

YGC

階段,

Global Concurrent Marking

階段的

Cleanup

FGC

階段 回收。

由於無法享受

LAB

帶來的最佳化,並且確定一片連續的記憶體空間需要掃描整堆

Heap

,因此確定巨型物件開始位置的成本非常高,如果可以,應用程式應避免生成巨型物件。

在分配

Humongous Object

之前先檢查是否超過 initiating heap occupancy percent (由引數

-XX:InitiatingHeapOccupancyPercent

控制) 和 the marking threshold。 如果超過的話,就啟動併發收集週期

Concurrent Marking Cycle

,為的是提早回收,防止

Evacuation Failure

Full GC

RSet

Remember Set

,已記憶集合)

在序列和並行收集器中,GC透過整堆掃描,來確定物件是否處於可達路徑中。

然而G1為了避免

STW

式的整堆

Heap

掃描,在每個分割槽

Region

記錄了一個已記憶集合(

RSet

),內部類似一個反向指標,記錄引用分割槽

Region

內物件的卡片

Card

的索引。

當要回收該分割槽

Region

時,透過掃描分割槽的RSet,來確定引用本分割槽內的物件是否存活,進而確定本分割槽內的物件存活情況。

事實上,並非所有的引用都需要記錄在

RSet

中,如果一個分割槽

Region

確定需要掃描,那麼無需

RSet

也可以無遺漏的得到引用關係。

那麼引用源自本分割槽

Region

的物件,當然不用落入

RSet

中;

同時,G1 GC每次都會對年輕代進行整體收集,因此引用源自年輕代的物件,也不需要在

RSet

記錄。

最後只有老年代的分割槽

Region

可能會有RSet記錄,這些分割槽稱為擁有RSet分割槽(an RSet’s owning region)。

Per Region Table

(PRT)

RSet

在內部使用

Per Region Table

(PRT)記錄分割槽

Region

的引用情況。 由於

RSet

的記錄要佔用分割槽

Region

的空間,如果一個分割槽非常“受歡迎”,那麼

RSet

佔用的空間會上升,從而降低分割槽

Region

的可用空間。 G1應對這個問題採用了改變

RSet

的密度的方式,在

PRT

中將會以三種模式記錄引用:

稀少:直接記錄引用物件的卡片

Card

的索引

細粒度:記錄引用物件的分割槽

Region

的索引

粗粒度:只記錄引用情況,每個分割槽對應一個位元位

由上可知,粗粒度的

PRT

只是記錄了引用數量,需要透過整堆

Heap

掃描才能找出所有引用,因此掃描速度也是最慢的。

CSet

Collection Set

,收集集合)

收集集合(

CSet

)代表每次GC暫停時回收的一系列目標分割槽

Region

在任意一次收集暫停中,

CSet

所有分割槽都會被釋放,內部存活的物件都會被轉移到分配的空閒分割槽中。

因此無論是年輕代收集,還是混合收集,工作的機制都是一致的。

年輕代收集(

YGC

)的

CSet

只容納年輕代分割槽,而混合收集(

Mixed GC

)會透過啟發式演算法,在老年代候選回收分割槽中,篩選出回收收益最高的分割槽新增到

CSet

中。

候選老年代分割槽的

CSet

准入條件,可以透過活躍度閾值

-XX:G1MixedGCLiveThresholdPercent

(預設85%)進行設定,從而攔截那些回收開銷巨大的物件;

同時,每次混合收集可以包含候選老年代分割槽,可根據

CSet

對堆的總大小佔比

-XX:G1OldCSetRegionThresholdPercent

(預設10%)設定數量上限。

由上述可知,G1的收集都是根據

CSet

進行操作的,年輕代收集(

YGC

)與混合收集(

Mixed GC

)沒有明顯的不同,最大的區別在於兩種收集的觸發條件。

年輕代收集集合

CSet of Young Collection

應用執行緒不斷活動後,年輕代空間會被逐漸填滿。當JVM分配物件到Eden區域失敗(Eden區已滿)時,便會觸發一次

STW

式的年輕代收集。 在年輕代收集中,Eden分割槽存活的物件將被複製到Survivor分割槽; 原有Survivor分割槽存活的物件,將根據任期閾值(tenuring threshold)分別晉升到

PLAB

中,新的survivor分割槽和老年代分割槽。而原有的年輕代分割槽將被整體回收掉。

同時,年輕代收集還負責維護物件的年齡(存活次數),輔助判斷老化(tenuring)物件晉升的時候是到Survivor分割槽還是到老年代分割槽。 年輕代收集首先先將晉升物件尺寸總和、物件年齡資訊維護到年齡表中,再根據年齡表、Survivor尺寸、Survivor填充容量

-XX:TargetSurvivorRatio

(預設50%)、最大任期閾值

-XX:MaxTenuringThreshold

(預設15),計算出一個恰當的任期閾值,凡是超過任期閾值的物件都會被晉升到老年代。

混合收集集合

CSet of Mixed Collection

年輕代收集不斷活動後,老年代的空間也會被逐漸填充。當老年代佔用空間超過整堆比IHOP閾值

-XX:InitiatingHeapOccupancyPercent

(預設45%)時,G1就會啟動一次混合垃圾收集週期。

為了滿足暫停目標,G1可能不能一口氣將所有的候選分割槽收集掉,因此G1可能會產生連續多次的混合收集與應用執行緒交替執行,每次

STW

的混合收集與年輕代收集過程相類似。

為了確定包含到年輕代收集集合CSet的老年代分割槽,JVM透過引數混合週期的最大總次數

-XX:G1MixedGCCountTarget

(預設8)、堆廢物百分比

-XX:G1HeapWastePercent

(預設5%)。

透過候選老年代分割槽總數與混合週期最大總次數,確定每次包含到

CSet

的最小分割槽數量;

根據堆廢物百分比,當收集達到引數時,不再啟動新的混合收集。而每次新增到

CSet

的分割槽,則透過計算得到的GC效率進行安排。

G1的活動週期

JVM G1(Garbage-First Garbage Collector)收集器全過程剖析

G1的垃圾回收包括了以下幾種:

Concurrent Marking Cycle (併發收集) 類似

CMS

的併發收集過程。

Young Collection (YGC,年輕代收集,

STW

Mixed Collection Cycle (混合收集,

STW

Full GC(FGC,

STW

JDK10以前FGC是序列回收,JDK10+可以是並行回收。

併發標記週期

Concurrent Marking Cycle

併發標記週期是G1中非常重要的階段,這個階段將會為混合收集週期識別垃圾最多的老年代分割槽。

整個週期完成根標記、識別所有(可能)存活物件,並計算每個分割槽的活躍度,從而確定GC效率等級。

當達到IHOP閾值

-XX:InitiatingHeapOccupancyPercent

(老年代佔整堆比,預設45%)時,便會觸發併發標記週期。

整個併發標記週期將由初始標記(Initial Mark)、根分割槽掃描(Root Region Scanning)、併發標記(Concurrent Marking)、重新標記(Remark)、清除(Cleanup)幾個階段組成。

其中,初始標記(隨年輕代收集一起活動)、重新標記、清除是STW的,

而併發標記如果來不及標記存活物件,則可能在併發標記過程中,G1又觸發了幾次年輕代收集(YGC)。

Initial Marking

(初始標記, STW)

它標記了從GC Root開始直接可達的物件。

事實上,當達到IHOP閾值時,G1並不會立即發起併發標記週期,而是等待下一次年輕代收集,利用年輕代收集的STW時間段,完成初始標記,這種方式稱為借道(Piggybacking)。

Root region scanning

(根分割槽掃描)

在初始標記暫停結束後,年輕代收集也完成的物件複製到Survivor的工作,應用執行緒開始活躍起來。此時為了保證標記演算法的正確性,所有新複製到Survivor分割槽的物件,都需要被掃描並標記成根,這個過程稱為根分割槽掃描(Root Region Scanning),同時掃描的Suvivor分割槽也被稱為根分割槽(Root Region)。

Concurrent Marking

(併發標記)

這個階段從GC Root開始對heap中的物件標記,標記執行緒與應用程式執行緒並行執行,並且收集各個

Region

的存活物件資訊。 和應用執行緒併發執行,併發標記執行緒在併發標記階段啟動,由引數

-XX:ConcGCThreads

(預設GC執行緒數的1/4,即-XX:ParallelGCThreads/4)控制啟動數量, 每個執行緒每次只掃描一個分割槽

Region

,從而標記出存活物件圖。

所有的標記任務必須在堆滿前就完成掃描,如果併發標記耗時很長,那麼有可能在併發標記過程中,又經歷了幾次年輕代收集。 如果堆滿前沒有完成標記任務,則會觸發擔保機制,經歷一次長時間的序列Full GC。

Remark

( 重新標記,STW)

標記那些在併發標記階段發生變化的物件,將被回收。 這個階段也是並行執行的,透過引數

-XX:ParallelGCThread

可設定GC暫停時可用的GC執行緒數。

Cleanup

(清理,STW)

清除階段主要執行以下操作:

RSet

梳理,啟發式演算法會根據活躍度和

RSet

尺寸對分割槽定義不同等級,同時

RSet

數理也有助於發現無用的引用。引數

-XX:+PrintAdaptiveSizePolicy

可以開啟列印啟發式演算法決策細節;

整理堆分割槽,為混合收集週期識別回收收益高(基於釋放空間和暫停目標)的老年代分割槽集合;

識別所有空閒分割槽,即發現無存活物件的分割槽。該分割槽可在清除階段直接回收,無需等待下次收集週期。

年輕代收集 Young Collection /混合收集週期 Mixed Collection Cycle

當應用執行開始時,堆記憶體可用空間還比較大,只會在年輕代滿時,觸發年輕代收集;

隨著老年代記憶體增長,當到達IHOP閾值

-XX:InitiatingHeapOccupancyPercent

(老年代佔整堆比,預設45%)時,G1開始著手準備收集老年代空間。

首先經歷併發標記週期

Concurrent Marking Cycle

,識別出高收益的老年代分割槽,前文已述。

但隨後G1並不會馬上開始一次混合收集,而是讓應用執行緒先執行一段時間,等待觸發一次年輕代收集。

在這次STW中,G1將保準整理混合收集週期。接著再次讓應用執行緒執行,當接下來的幾次年輕代收集時,將會有老年代分割槽加入到CSet中,

即觸發混合收集,這些連續多次的混合收集稱為混合收集週期(

Mixed Collection Cycle

)。

年輕代收集 Young Collection,

YGC

每次收集過程中,既有並行執行的活動,也有序列執行的活動,但都可以是多執行緒的。

在並行執行的任務中,如果某個任務過重,會導致其他執行緒在等待某項任務的處理,需要對這些地方進行最佳化。

以下部分部分可以結合日誌檢視

並行活動

外部根分割槽掃描 Ext Root Scanning: 此活動對堆外的根(JVM系統目錄、VM資料結構、JNI執行緒控制代碼、硬體暫存器、全域性變數、執行緒對棧根)進行掃描,發現那些沒有加入到暫停收集集合CSet中的物件。如果系統目錄(單根)擁有大量載入的類,最終可能其他並行活動結束後,該活動依然沒有結束而帶來的等待時間。

更新已記憶集合 Update RS: 併發最佳化執行緒會對髒卡片的分割槽進行掃描更新日誌緩衝區來更新RSet,但只會處理全域性緩衝列表。作為補充,所有被記錄但是還沒有被最佳化執行緒處理的剩餘緩衝區,會在該階段處理,變成已處理緩衝區(Processed Buffers)。為了限制花在更新RSet的時間,可以設定暫停佔用百分比

-XX:G1RSetUpdatingPauseTimePercent

(預設10%,即-XX:MaxGCPauseMills/10)。值得注意的是,如果更新日誌緩衝區更新的任務不降低,單純地減少RSet的更新時間,會導致暫停中被處理的緩衝區減少,將日誌緩衝區更新工作推到併發最佳化執行緒上,從而增加對Java應用執行緒資源的爭奪。

RSet掃描 Scan RS: 在收集當前CSet之前,考慮到分割槽外的引用,必須掃描CSet分割槽的RSet。如果RSet發生粗化,則會增加RSet的掃描時間。 開啟診斷模式

-XX:UnlockDiagnosticVMOptions

後, 透過引數

-XX:+G1SummarizeRSetStats

可以確定併發最佳化執行緒是否能夠及時處理更新日誌緩衝區,並提供更多的資訊,來幫助為RSet粗化總數提供視窗。 引數

-XX:G1SummarizeRSetStatsPeriod=n

可設定RSet的統計週期,即經歷多少此GC後進行一次統計

程式碼根掃描 Code Root Scanning:對程式碼根集合進行掃描,掃描JVM編譯後代碼Native Method的引用資訊(nmethod掃描),進行RSet掃描。事實上,只有CSet分割槽中的RSet有強程式碼根時,才會做nmethod掃描,查詢對CSet的引用。

轉移和回收 Object Copy: 透過選定的CSet以及CSet分割槽完整的引用集,將執行暫停時間的主要部分:CSet分割槽存活物件的轉移、CSet分割槽空間的回收。透過工作竊取機制來負載均衡地選定複製物件的執行緒,並且複製和掃描物件被轉移的存活物件將複製到每個GC執行緒分配緩衝區GCLAB。G1會透過計算,預測分割槽複製所花費的時間,從而調整年輕代的尺寸。

終止 Termination: 完成上述任務後,如果任務佇列已空,則工作執行緒會發起終止要求。如果還有其他執行緒繼續工作,空閒的執行緒會透過工作竊取機制嘗試幫助其他執行緒處理。而單獨執行根分割槽掃描的執行緒,如果任務過重,最終會晚於終止。

GC外部的並行活動 GC Worker Other: 該部分並非GC的活動,而是JVM的活動導致佔用了GC暫停時間(例如JNI編譯)。

序列活動

程式碼根更新 Code Root Fixup:根據轉移物件更新程式碼根。

程式碼根清理 Code Root Purge:清理程式碼根集合表。

清除全域性卡片標記 Clear CT:在任意收集週期會掃描CSet與RSet記錄的PRT,掃描時會在全域性卡片表中進行標記,防止重複掃描。在收集週期的最後將會清除全域性卡片表中的已掃描標誌。

選擇下次收集集合 Choose CSet:該部分主要用於併發標記週期後的年輕代收集、以及混合收集中,在這些收集過程中,由於有老年代候選分割槽的加入,往往需要對下次收集的範圍做出界定;但單純的年輕代收集中,所有收集的分割槽都會被收集,不存在選擇。

引用處理 Ref Proc:主要針對軟引用、弱引用、虛引用、final引用、JNI引用。當Ref Proc佔用時間過多時,可選擇使用引數

-XX:ParallelRefProcEnabled

啟用多執行緒引用處理。G1希望應用能小心使用軟引用,因為軟引用會一直佔據記憶體空間直到空間耗盡時被Full GC回收掉;即使未發生Full GC,軟引用對記憶體的佔用,也會導致GC次數的增加。

引用排隊 Ref Enq:此項活動可能會導致RSet的更新,此時會透過記錄日誌,將關聯的卡片標記為髒卡片。

卡片重新髒化 Redirty Cards:重新髒化卡片。

回收空閒巨型分割槽 Humongous Reclaim:G1做了一個最佳化:透過檢視所有根物件以及年輕代分割槽的RSet,如果確定RSet中巨型物件沒有任何引用,則說明G1發現了一個不可達的巨型物件,該物件分割槽會被回收。

釋放分割槽 Free CSet:回收CSet分割槽的所有空間,並加入到空閒分割槽中。

其他活動 Other:GC中可能還會經歷其他耗時很小的活動,如修復JNI控制代碼等。

併發標記週期後的年輕代收集 Young Collection Following Concurrent Marking Cycle

當G1發起併發標記週期之後,並不會馬上開始混合收集。 G1會先等待下一次年輕代收集,然後在該收集階段中,確定下次混合收集的CSet(Choose CSet)。

混合收集週期 Mixed Collection Cycle,

Mixed GC

單次的混合收集與年輕代收集並無二致。

根據暫停目標,老年代的分割槽可能不能一次暫停收集中被處理完,G1會發起連續多次的混合收集,稱為混合收集週期(Mixed Collection Cycle)。

G1會計算每次加入到CSet中的分割槽數量、混合收集進行次數,並且在上次的年輕代收集、以及接下來的混合收集中,G1會確定下次加入CSet的分割槽集(Choose CSet),並且確定是否結束混合收集週期。

轉移失敗的擔保機制

Full GC

轉移失敗(

Evacuation Failure

)是指當G1無法在堆空間中申請新的分割槽時,G1便會觸發擔保機制,執行一次

STW

式的、單執行緒(JDK10支援多執行緒)的Full GC。

JVM G1(Garbage-First Garbage Collector)收集器全過程剖析

Full GC會對整堆做標記清除和壓縮,最後將只包含純粹的存活物件。引數

-XX:G1ReservePercent

(預設10%)可以保留空間,來應對晉升模式下的異常情況,最大佔用整堆50%,更大也無意義。

G1在以下場景中會觸發Full GC,同時會在日誌中記錄

to-space exhausted

以及

Evacuation Failure

從年輕代分割槽複製存活物件時,無法找到可用的空閒分割槽

從老年代分割槽轉移存活物件時,無法找到可用的空閒分割槽

分配巨型物件

Humongous Object

時在老年代無法找到足夠的連續分割槽

由於G1的應用場合往往堆記憶體都比較大,所以Full GC的收集代價非常昂貴,應該避免Full GC的發生。

問題

什麼時候觸發concurrent marking ?

# 啟動併發週期 Concurrent Marking Cycle (以及後續的混合週期 MixedGC)時的堆記憶體佔用百分比。 G1用它來觸發併發GC週期,基於整個堆的使用率,而不只是某一代記憶體的使用比例。預設45%

# 當堆存活物件佔用堆的45%,就會啟動G1 中併發標記週期 Concurrent Marking Cycle

-XX:InitiatingHeapOccupancyPercent

什麼時候發生Mixed GC? concurrent marking 主要是為Mixed GC提供標記服務的,並不是一次GC過程的一個必須環節。

由一些引數控制,另外也控制著哪些老年代Region會被選入CSet(收集集合)。

# 一次 concurrent marking之後,最多執行Mixed GC的次數(預設8)

-XX:G1MixedGCCountTarget

# 堆廢物百分比(預設5%),在每次YGC之後和再次發生Mixed GC之前,會檢查垃圾佔比是否達到此引數,只有達到了,下次才會發生Mixed GC。

-XX:G1HeapWastePercent

# old generation region中的存活物件的佔比,只有在此引數之下,才會被選入CSet。

-XX:G1MixedGCLiveThresholdPercent

# 一次Mixed GC中能被選入CSet的最多old generation region數量。

-XX:G1OldCSetRegionThresholdPercent

GC日誌詳解

併發標記週期 Concurrent Marking Cycle

[GC concurrent-root-region-scan-start]

[GC concurrent-root-region-scan-end, 0。0094252 secs]

# 根分割槽掃描,可能會被 YGC 打斷,那麼結束就是如:[GC pause (G1 Evacuation Pause) (young)[GC concurrent-root-region-scan-end, 0。0007157 secs]

[GC concurrent-mark-start]

[GC concurrent-mark-end, 0。0203881 secs]

# 併發標記階段

[GC remark [Finalize Marking, 0。0007822 secs] [GC ref-proc, 0。0005279 secs] [Unloading, 0。0013783 secs], 0。0036513 secs]

# 重新標記,STW

[Times: user=0。01 sys=0。00, real=0。00 secs]

[GC cleanup 13985K->13985K(20480K), 0。0034675 secs]

[Times: user=0。00 sys=0。00, real=0。00 secs]

# 清除

年輕代收集 YGC

[GC pause (G1 Evacuation Pause) (young), 0。0022483 secs]

# young -> 年輕代 Evacuation-> 複製存活物件

[Parallel Time: 1。0 ms, GC Workers: 10] # 併發執行的GC執行緒數,以下階段是併發執行的

[GC Worker Start (ms): Min: 109。0, Avg: 109。1, Max: 109。1, Diff: 0。2]

[Ext Root Scanning (ms): Min: 0。1, Avg: 0。2, Max: 0。3, Diff: 0。2, Sum: 2。3] # 外部根分割槽掃描

[Update RS (ms): Min: 0。0, Avg: 0。0, Max: 0。0, Diff: 0。0, Sum: 0。0] # 更新已記憶集合 Update RSet,檢測從年輕代指向老年代的物件

[Processed Buffers: Min: 0, Avg: 0。0, Max: 0, Diff: 0, Sum: 0]

[Scan RS (ms): Min: 0。0, Avg: 0。0, Max: 0。0, Diff: 0。0, Sum: 0。0]# RSet掃描

[Code Root Scanning (ms): Min: 0。0, Avg: 0。0, Max: 0。0, Diff: 0。0, Sum: 0。1] # 程式碼根掃描

[Object Copy (ms): Min: 0。3, Avg: 0。3, Max: 0。4, Diff: 0。1, Sum: 3。5] # 轉移和回收,複製存活的物件到survivor/old區域

[Termination (ms): Min: 0。0, Avg: 0。0, Max: 0。0, Diff: 0。0, Sum: 0。0] # 完成上述任務後,如果任務佇列已空,則工作執行緒會發起終止要求。

[Termination Attempts: Min: 1, Avg: 5。8, Max: 9, Diff: 8, Sum: 58]

[GC Worker Other (ms): Min: 0。0, Avg: 0。0, Max: 0。0, Diff: 0。0, Sum: 0。1] # GC外部的並行活動,該部分並非GC的活動,而是JVM的活動導致佔用了GC暫停時間(例如JNI編譯)。

[GC Worker Total (ms): Min: 0。5, Avg: 0。6, Max: 0。7, Diff: 0。2, Sum: 5。9]

[GC Worker End (ms): Min: 109。7, Avg: 109。7, Max: 109。7, Diff: 0。0]

[Code Root Fixup: 0。0 ms] # 序列任務,根據轉移物件更新程式碼根

[Code Root Purge: 0。0 ms] #序列任務, 程式碼根清理

[Clear CT: 0。5 ms] #序列任務,清除全域性卡片 Card Table 標記

[Other: 0。8 ms]

[Choose CSet: 0。0 ms] # 選擇下次收集集合 CSet

[Ref Proc: 0。4 ms] # 引用處理 Ref Proc,處理軟引用、弱引用、虛引用、final引用、JNI引用

[Ref Enq: 0。0 ms] # 引用排隊 Ref Enq

[Redirty Cards: 0。3 ms] # 卡片重新髒化 Redirty Cards:重新髒化卡片

[Humongous Register: 0。0 ms]

[Humongous Reclaim: 0。0 ms] # 回收空閒巨型分割槽 Humongous Reclaim,透過檢視所有根物件以及年輕代分割槽的RSet,如果確定RSet中巨型物件沒有任何引用,該物件分割槽會被回收。

[Free CSet: 0。0 ms] # 釋放分割槽 Free CSet

[Eden: 12288。0K(12288。0K)->0。0B(11264。0K) Survivors: 0。0B->1024。0K Heap: 12288。0K(20480。0K)->832。0K(20480。0K)]

[Times: user=0。01 sys=0。00, real=0。00 secs]

# 從年輕代分割槽複製存活物件時,無法找到可用的空閒分割槽

# 從老年代分割槽轉移存活物件時,無法找到可用的空閒分割槽 這兩種情況之一導致的 YGC

[GC pause (G1 Evacuation Pause) (young) (to-space exhausted), 0。0916534 secs]

# 併發標記週期 Concurrent Marking Cycle 中的 根分割槽掃描階段,被 YGC中斷

[GC pause (G1 Evacuation Pause) (young)[GC concurrent-root-region-scan-end, 0。0007157 secs]

混合收集週期 Mixed Collection Cycle, Mixed GC

# 併發標記週期 Concurrent Marking Cycle 的開始

[GC pause (G1 Evacuation Pause) (young) (initial-mark) , 0。0443460 secs]

Full GC

[Full GC (Allocation Failure) 20480K->9656K(20480K), 0。0189481 secs]

[Eden: 0。0B(1024。0K)->0。0B(5120。0K) Survivors: 0。0B->0。0B Heap: 20480。0K(20480。0K)->9656。8K(20480。0K)], [Metaspace: 4960K->4954K(1056768K)]

[Times: user=0。03 sys=0。00, real=0。02 secs]

參考資料

https://www。

oracle。com/technical-re

sources/articles/java/g1gc。html

JDK8 G1:

https://

docs。oracle。com/javase/

8/docs/technotes/guides/vm/gctuning/g1_gc。html

Other Blog:

https://www。

infoq。com/articles/G1-O

ne-Garbage-Collector-To-Rule-Them-All/

https://

tech。meituan。com/2016/0

9/23/g1。html

https://www。

infoq。com/articles/tuni

ng-tips-G1-GC/

https://

blog。csdn。net/coderlius

/article/details/79272773

https://www。

cnblogs。com/webor2006/p

/11146273。html

https://www。

cnblogs。com/webor2006/p

/11147545。html

[1] Charlie H, Monica B, Poonam P, Bengt R。 Java Performance Companion

[2] 周志明。 深入理解JVM虛擬機器

by Sven Augustus

https://

my。oschina。net/langxSpi

rit

標簽: 分割槽  GC  收集  G1  CSet