JVM垃圾收集器——G1
導航
- 引言
- 一、G1 介紹
- 1.1 適用場景
- 1.2 設計初衷
- 1.3 關注焦點
- 1.4 工作模式
- 1.5 堆的邏輯結構
- 1.6 主要收集目標
- 1.7 停頓預測模型
- 1.8 拷貝和壓縮
- 1.9 與 CMS 和 Parallel 收集器的比較
- 1.10 固定停頓目標
- 二、堆的邏輯分區
- 2.1 region
- 2.2 CSet
- 2.3 RSet
- 2.4 Card Table
- 三、G1 的工作原理
- 3.1 YGC
- 3.2 Mixed GC
- 3.3 工作流程
- 四、常用的G1調優參數和建議
- 4.1 G1常見命令行參數及其默認值
- 4.2 調優建議
- 4.3 to-space 的溢出和耗盡
- 4.4 Humongous 對象及其分配
引言
本文針對 Hotspot 虛擬機的 G1 垃圾收集器進行總結和歸納,適用于JDK 8。
文章內容以 Garbage-First Garbage Collector 和 Garbage-First Garbage Collector Tuning 兩篇官方文章為主,結合 JVM面試必問:G1垃圾回收器 技術社區分享匯總。
一、G1 介紹
G1 是 Hotspot 虛擬機在 jdk 8 之后正式(java7處于試驗階段)推出的一款針對多核CPU、大內存的服務器端應用程序提供服務的垃圾收集器。
1.1 適用場景
通用需要:大堆空間(>6G)、低延遲(< 0.5s)
業務需要:堆中有 超過50% 的存活對象,對象的分配率和提升率差異很大。
1.2 設計初衷
實現高吞吐量的同時獲得盡可能短暫的STW停頓。
1.3 關注焦點
為用戶提供一種更大的堆且低延遲的解決方案。例如,6G的堆,且延遲基本可以穩定在小于0.5秒。
1.4 工作模式
面向整個堆空間進行GC,某些階段如并發標記,可與工作線程并發執行。
1.5 堆的邏輯結構
整個堆被分成一個個大小相等且物理地址連續的 region(分區)。region大小在1M~32M不等(2的冪),取決于堆的大小,但總數不會超過2048。
為兼容傳統的分代機制,eden、survivor、old 這些年代概念依然存在,但在G1中是以 region 為單位,分散在堆中(相同或不同的年代之間不一定連續)。
1.6 主要收集目標
Garbage First 垃圾優先。
通過全局的并發標記,G1可以得知哪些區域大部分都是垃圾,G1會首先收集和壓縮這些region。優先收集垃圾更多的 region,就意味著可以以更短的時間獲得更大的收益。
1.7 停頓預測模型
G1會使用一種稱為“暫停預測”的模型,來評估需要收集多少region,從而限制停頓時間(STW)。
1.8 拷貝和壓縮
對于內存碎片問題,G1在GC的過程中會將一個或多個 region中的存活對象拷貝、壓縮到一個新的region 中,以減少堆空間的碎片化。
這項操作必須是 STW的,因為要拷貝到新的region,如果應用程序不停的分配對象,很明顯會嚴重干擾拷貝壓縮的過程。
這個過程會在多處理器上并行執行以降低停頓時長。因此,每次GC,G1都在努力減少內存碎片。
1.9 與 CMS 和 Parallel 收集器的比較
G1要優于這兩種收集器。
CMS 默認是不進行內存壓縮的,所以會產生嚴重的內存碎片問題;
Parallel收集器會對整個堆進行數據壓縮,這會導致嚴重的STW。
1.10 固定停頓目標
用戶可以設定STW的停頓時間,但G1只是 大概率能夠滿足而并非絕對 。它會根據之前GC的情況,會構建一個相當精確的“收集成本模型”,以此來決定要收集哪些region以及收集多少region。
a reasonably accurate model of the cost of collecting the regions
二、堆的邏輯分區
2.1 region
上圖是Oracle官網給出的G1收集器對整個堆結構的邏輯劃分示例。
G1 將整個堆劃分為一個個相同大小的 region。它覆蓋了一塊連續的內存空間,大小在 1M ~ 32M不定,但一定是 2 的冪,默認數量是2048個。
和傳統的垃圾收集器,如Serial、Parallel、CMS等等不同,G1只是在邏輯上分代。
也就是說,G1摒棄了傳統的大塊分代空間的做法,以 region 為單元,將 region標記為年輕代或老年代,這些region共同組成年輕代或老年代的整體大小。而且G1可以隨著運行情況和策略的變化動態調整年輕代或老年代region的數量以此來調整對應分代的大小。
2.2 CSet
CSet ,Collection Set ,它是一組可被回收的 region 集合。在后面講到的 YGC 中,會提到它的使用。CSet 中的 region 既可以是年輕代,也可以是老年代,G1 會將 CSet中存活的對象拷貝、壓縮到新的region,CSet 大概占用堆空間的 1% 大小。
2.3 RSet
對于多數分代收集器,YGC 會先收集堆中年輕代的內存空間,這是一種增量收集的方式。G1作為邏輯分代的收集器,同樣屬于增量式垃圾收集器。
在進行增量收集的時候,收集器需要知道未收集的對象有沒有指向正在收集的對象的引用。對于 YGC 來說,通常是老年代指向年輕代。而 RSet 就是這樣一種記憶結構,專門用于存儲一組其他對象指向當年對象的引用。
對于 G1 而言,RSet 是位于 region 中。之所以設計這樣一個結構,就是為了在做 GC 標記的時候,避免全圖掃描。這也是 G1 高效 GC 的關鍵手段之一。
2.4 Card Table
Card Table 是一中特殊類型的 RSet。
A card table is a particular type of remembered set. —— Card Tables and Concurrent Phases
Hotspot 虛擬機使用一組字節數組來實現 Card Table。每個字節代表一個 card,card又對應堆中的一塊連續內存地址。如果引用關系發生改變,就將對應的 card 標記為 dirty,在 remark 階段就只需要掃描這些 dirty card 就可以快速追蹤引用發生變化的對象。
三、G1 的工作原理
G1的工作過程是一個非常復雜的流程,包含很多可考慮的因素和條件。
3.1 YGC
YGC 又叫做 young collection 或 Minor GC,會有STW停頓。它是G1收集器的先鋒兵,實際上不光是G1,其他收集器在最初發生GC的時候都要先進行 YGC。
Java中的大多數對象都主要分配在堆的 Eden 區,它隸屬于年輕代,同時又隨著應用程序的運行快速“消亡”(對象不可達),因此 YGC 是回收這些對象內存空間的有效手段。
在 YGC 期間,G1會從 CSet 中獲取需要收集的 eden 和 survivor 區。CSet 是 Collection Set 的縮寫,它是用于記錄需要做垃圾收集的 region集合。G1 通過將活動對象從 eden 和 survivor 區增量并行復制到一個或多個不同的新 region 來實現內存壓縮,減少碎片。
不同對象的目標region取決于對象的分代年齡,當達到年齡閾值,就會分配到老年代 region 中,而年齡不足的,會被分配到 survivor region中,并被包含在下一次的YGC 或 Mixed GC的CSet中。
值得一提的是,在STW的YGC期間,還會額外做一些有備后用的操作,如初始標記,這個過程是后續執行并發標記的“必要前戲”,目標是標記出 GC roots 根節點,由于本身就需要STW,因此放在YGC的STW中完成是希望能夠充分利用停頓時間,以此達到G1的設計目標——pause time goal。
3.2 Mixed GC
Mixed GC 是在 YGC 的基礎之上,選擇性的增加 old region 的清理。
在 YGC 的過程中,G1會進行初始標記工作,之后,G1的垃圾回收線程就會進入重要的并發標記環節。
concurrent mark 階段是與用戶線程并發執行的垃圾收集工作,可以被 YGC 的 STW 打斷。其目標是標記出所有可達的對象(三色標記算法+RSet)。
Concurrent marking phase: The G1 GC finds reachable (live) objects across the entire heap. This phase happens concurrently with the application, and can be interrupted by STW young garbage collections.
G1的設計目標是盡可能在用戶設定的停頓時間之內完成垃圾收集,為了達到這一點,并發執行一部分工作是必要的,但帶來的問題就是,并發工作的GC線程勢必會占用原本應該去執行用戶線程的CPU資源,造成的損失就是吞吐量的下降(官方給出的參考數據是 90%的用戶線程時間和 10%的GC時間,而Parallel收集器的數據是 99% 的用戶線程時間和 1%的GC時間)。
在并發標記階段完成后,G1 的 YGC就會切換到 Mixed GC。
Mixed GC 階段,G1會選擇性的將一些 old region 加入到待收集的CSet 中,隨 eden、survivor一同被G1清理。當G1清理了足夠的 old region后(多次Mixed GC),G1就再次切換回 YGC,直到下次并發標記完成。
總之,G1的GC過程可以用這樣一段描述來概括:
一開始G1使用YGC進行年輕代的垃圾清理,拷貝、壓縮存活的對象,但當堆內存可用空間漸漸不足,例如活動對象占用超過整個堆的45%這一閾值時,就會觸發G1的標記周期,這一過程以并發標記為主,當標記結束后,G1啟動 MixedGC,它不僅會清理年輕代,還會選擇性的清理一些老年代,當經過了幾輪的 MixedGC之后,G1已經收集了足夠多的老年代區域,此時堆的空間也不再緊張,G1就會再次切回YGC進行垃圾回收。
3.3 工作流程
YGC 和 Mixed GC 實際上是 G1 的兩種工作模式,對于 Mixed GC,完整的工作流程應該包含以下幾個階段。
初始標記(Initial Mark): 這個階段僅僅只是標記GC Roots能直接關聯到的對象并修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶程序并發運行時,能在正確的可用的Region中創建新對象,這階段需要停頓線程,但是耗時很短。而且是借用進行Minor GC的時候同步完成的,所以G1收集器在這個階段實際并沒有額外的停頓
并發標記(Concurrent Mark):從GC Roots開始對堆中對象進行可達性分析,遞歸掃描整個堆里的對象圖,找出存活的對象,這階段耗時較長,但是可以與用戶程序并發執行。當對象圖掃描完成以后,還要重新處理SATB記錄下的在并發時有引用變動的對象。
最終標記(Final Mark): 對用戶線程做另一個短暫的暫停,用于處理并發階段結束后仍遺留下來的最后那少量的 SATB 記錄。
篩選回收(Live Data Counting and Evacuation): 負責更新 Region 的統計數據,對各個 Region 的回收價值和成本進行排序,根據用戶所期望的停頓時間來制定回收計劃。可以自由選擇多個Region來構成會收集,然后把回收的那一部分Region中的存活對象疏散到新的 region中。
四、常用的G1調優參數和建議
G1 是一個自適應的垃圾收集器,一些默認參數足可以高效工作。以下是 G1 相關的控制參數,了解這些參數的含義可以幫助我們更好的理解 G1 收集器。
4.1 G1常見命令行參數及其默認值
參考:Important Defaults
| -XX:G1HeapRegionSize=n | 設置 G1 的region的大小,1M ~ 32M ,2的冪。 目標是以-Xms為準大約有 2048 個 region |
| -XX:MaxGCPauseMillis=200 | 設置期望的最大停頓時間。默認為 200ms |
| -XX:G1NewSizePercent=5 | 年輕代占總堆的最小百分比。默認 5%。該參數為實驗參數,必須在該參數前面設置-XX:+UnlockExperimentalVMOptions 解鎖實驗參數,才能生效 |
| -XX:G1MaxNewSizePercent=60 | 年輕代占總堆最大百分比,應和上一條參數互補。 |
| -XX:ParallelGCThreads=n | 設置 STW 時垃圾收集線程數,將 n 設置為邏輯處理器的數量,但最大不要超過 8。如果邏輯處理器數量超過8,可以將 n 設置為邏輯處理器的 5/8 。 |
| -XX:ConcGCThreads=n | 設置并行標記的線程數??梢詫?n 設置為 ParallelGCThreads 參數的 1/4 。 |
| -XX:InitiatingHeapOccupancyPercent=45 | 設置足以觸發初始標記的堆占用閾值。默認為總堆大小的 45%。 |
| -XX:G1MixedGCLiveThresholdPercent=85 | 設置 Mixed GC 開始回收老年代 region 的堆占用閾值,默認為 85%。這是一個實驗性參數,須在該參數前面設置-XX:+UnlockExperimentalVMOptions 解鎖實驗參數,才能生效。 |
| -XX:G1HeapWastePercent=5 | 設置允許浪費的堆空間占比 |
| -XX:G1OldCSetRegionThresholdPercent=10 | 設置可以被 Mixed GC 回收的老年代 region 占比上限。默認為 10% |
| -XX:G1ReservePercent=10 | 設置預留的空內存大小,以防 to survivor 區溢出。默認為 10%。如果你要調整該參數,請記得將總堆的大小也一同調整。 |
4.2 調優建議
避免顯式設置年輕代的大小,因為 G1 會根據停頓目標自動調整年輕代的分配大小。
任何垃圾收集器都存在吞吐量和延遲的權衡。
G1 GC 的吞吐量目標是 90% 的應用程序時間和 10% 的垃圾收集時間。而 Parallel 收集器是 99% 的應用程序時間和 1% 的垃圾收集時間。因此,如果想要優化吞吐量,就必須要降低延遲的標準。
當你在調試 Mixed GC時,可以測試以下這些參數:
-XX:InitiatingHeapOccupancyPercent:改變觸發標記周期的閾值
-XX:G1MixedGCLiveThresholdPercent 和 -XX:G1HeapWastePercent:改變 Mixed GC 的決策
-XX:G1MixedGCCountTarget 和 -XX:G1OldCSetRegionThresholdPercent :調整 CSet 中老年代的數量
4.3 to-space 的溢出和耗盡
如果GC日志中出現"to-space overflow"或"to-space exhausted"這樣的消息,就表示G1 GC沒有足夠的內存來存放 survivor 或提升的對象。
這種情況可能是由于堆已經達到了最大值。例如:
924.897: [GC pause (G1 Evacuation Pause) (mixed) (to-space exhausted), 0.1957310 secs] 924.897: [GC pause (G1 Evacuation Pause) (mixed) (to-space overflow), 0.1957310 secs]可以嘗試調整以下參數:
- -XX:G1ReservePercent:調大該參數(以及相應的總堆大小)以增加 “to-space” 的預留空間。
- -XX:InitiatingHeapOccupancyPercent:調小該參數,以更早的觸發標記過程。
- -XX:ConcGCThreads:調大該參數,以增加并行標記線程的數量。
4.4 Humongous 對象及其分配
humongous 意為“極大的,碩大的”。對于 G1 來說,只要對象大小超過了 region 的一半,都稱為 humongous 對象。
humongous 對象會被直接分配到老年代的 humongous region,有時 humongous 對象可能會跨越多個 region 分區。
在G1分配 humongous 之前,都會檢查標記閾值,如果有必要,就會觸發初始標記,為 Mixed GC 做準備。
為了減少拷貝的開銷,一般不會輕易拷貝壓縮 humongous 對象,G1 會在恰當的時機壓縮 humongous 對象。
如果發現因為 humongous 對象的分配而頻繁觸發Mixed GC,并且老年代還在不斷飆升,那么可以適當增加 region 的大小,這樣,以前的 humongous 對象就有可能不會超過 region 的一半,從而遵循常規分配策略。
總結
以上是生活随笔為你收集整理的JVM垃圾收集器——G1的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 扫地机器人单扫和双扫_小米扫拖机器人体验
- 下一篇: tt协议号服务器,TTIot: TTIo