Java GC G1 详解
多看看JDK的JEP(JDK Enhancement Proposal),少看網上的垃圾文章。
通過JEP你可以知道,某個特性產生的背景是什么,是怎么解決的問題。
G1(Garbage-First)回收器是在JDK1.7中正式使用的全新垃圾回收器,G1擁有獨特的垃圾回收策略,從分代上看,G1依然屬于分代垃圾回收器,它會區分年代和老年代,依然有eden和survivor區,但從堆的結構上看,它并不要求整個eden區、年清代或者老年代都連續。它使用了全新的分區算法。其特點如下:
-
并行性:G1在回收期間,可以由多個GC線程同時工作,有效利用多核計算能力。
-
并發性:G1擁有與應用程序交替執行的能力,因此一般來說,不會在整個回收期間完全阻塞應用程序。
-
分代GC:與之前回收器不同,其他回收器,它們要么工作在年輕代要么工作在老年代。G1可以同時兼顧年輕代與老年代。
-
空間整理:G1在回收過程中,會進行適當的對象移動,不像CMS,只是簡單的標記清除,在若干次GC后CMS必須進行一次碎片整理,G1在每次回收時都會有效的復制對象,減少空間碎片。
-
可預見性:由于分區的原因,G1可以只選取部分區域進行內存回收,這樣縮小了回收范圍,因此對于全局停頓也能得到更好的控制。
一、G1的內存劃分和主要收集過程
G1收集回收器將堆進行分區,劃分為一個個的區域,每次收集的時候,只收集其中幾個區域,以此來控制垃圾回收產生一次停頓時間。
G1的收集過程可能有4個階段:
- 新生代GC
- 并發標記周期
- 混合收集
- (如果需要)進行Full GC。
二、G1的新生代GC
新生代GC的主要工作是回收eden區和survivor區。一旦eden區被占滿,新生代GC就會啟動。新生代GC收集前后的堆數據如圖5.6所示,其中E表示eden區,S表示survivor區,O表示老年代。可以看到,新生代GC只處理eden和survivor區,回收后,所有的eden區都應該被清空,而survivor區會被收集一部分數據,但是應該至少仍然存在一個survivor區,類比其他的新生代收集器,這一點似乎并沒有太大變化。另一個重要的變化是老年代的區域增多,因為部分survivor區或者eden區的對象可能會晉升到老年代。
三、G1并發標記周期
G1的并發階段和CMS有些類似,它們都是為了降低一次停頓時間,而將可以和應用程序并發執行的部分單獨提取出來執行。
并發標記周期針對老年代
并發標記周期可分為以下幾步:
-
初始標記:標記從根節點直接可達的對象。這個階段會伴隨一次新生代GC,它是會產生全局停頓的,應用程序在這個階段必須停止執行。
-
根區域掃描:由于初始標記必然會伴隨一次新生代GC,所以在初始化標記后,eden被清空,并且存活對象被移到survivor區。在這個階段,將掃描由survivor區直接可達的老年代區域,并標記這些直接可達的對象。 這個過程是可以和應用程序并發執行的。但是根區域掃描不能和新生代GC同時發生(因為根區域掃描依賴survivor區的對象,而新生代GC會修改這個區域),故如果恰巧此時需要新生代GC,GC就需要等待根區域掃描結束后才能進行,如果發生這種情況,這次新生代GC的時間就會延長。
-
并發標記:和CMS類似,并發標記將會掃描并查找整個堆的存活對象,并做好標記。這是一個并發過程,并且這個過程可以被一次新生代GC打斷。
-
重新標記:和CMS一樣,重新標記也是會使應用程序停頓,由于在并發標記過程中,應用程序依然運行,因此標記結果可能需要修正,所以在此階段對上一次標記進行補充。在G1中,這個過程使用SATB(Snapshot-At-The-Begining)算法完成,即G1會在標記之初為存活對象創建一個快照,這個快照有助于加速重新標記的速度。
-
獨占清理:顧名思義,這個階段會引起停頓。它將計算各個區域的存活對象和GC回收比例并進行排序,識別可供混合回收的區域。在這個階段,還會更新記憶集。該階段給出了需要被混合回收的區域并進行了標記,在混合回收階段,需要這些信息。
-
并發清理階段:識別并清理完全空閑的區域。它是并發的清理,不會引起停頓。
全稱是Snapshot-At-The-Beginning,由字面理解,是GC開始時活著的對象的一個快照。它是通過Root Tracing得到的,作用是維持并發GC的正確性。
那么它是怎么維持并發GC的正確性的呢?根據三色標記算法,我們知道對象存在三種狀態:
白:對象沒有被標記到,標記階段結束后,會被當做垃圾回收掉。
灰:對象被標記了,但是它的field還沒有被標記或標記完。
黑:對象被標記了,且它的所有field也被標記完了。
SATB 利用 write barrier 將所有即將被刪除的引用關系的舊引用記錄下來,最后以這些舊引用為根 Stop The World 地重新掃描一遍即可避免漏標問題。 因此G1 Remark階段 Stop The World 與 CMS了的remark有一個本質上的區別,那就是這個暫停只需要掃描有 write barrier 所追中對象為根的對象, 而 CMS 的remark 需要重新掃描整個根集合,因而CMS remark有可能會非常慢。
四、混合回收
在并發標記周期中,雖有部分對象被回收,但是回收的比例是非常低的。但是在并發標記周期后,G1已經明確知道哪些區域含有比較多的垃圾對象,在混合回收階段,就可以專門針對這些區域進行回收。當然G1會優先回收垃圾比例較高的區域(回收這些區域的性價比高),這正是G1名字的由來(Garbage First Garbage Collector:譯為垃圾優先的垃圾回收器),這里的垃圾優先(Garbage First)指的是回收時優先選取垃圾比例最高的區域。
這個階段叫做混合回收,是因為在這個階段,即會執行正常的年輕代GC,又會選取一些被標記的老年代區域進行回收,同時處理了新生代和老年代。
混合回收會被執行多次,直到回收了足夠多的內存空間,然后,它會觸發一次新生代GC。新生代GC后,又可能會發生一次并發標記周期的處理,最后又會引起混合回收,因此整個過程可能是如下圖:
五、必要時的Full GC
和CMS類似,并發收集讓應用程序和GC線程交替工作,因此在特別繁忙的情況下無可避免的會發生回收過程中內存不足的情況,當遇到這種情況,G1會轉入一個Full GC 進行回收。
以下4種情況會觸發這類的Full GC:
1、并發模式失效
G1啟動標記周期,但在Mix GC之前,老年代就被填滿,這時候G1會放棄標記周期。這種情形下,需要增加堆大小,或者調整周期(例如增加線程數-XX:ConcGCThreads等)。
GC日志如下的示例:
解決辦法:發生這種失敗意味著堆的大小應該增加了,或者G1收集器的后臺處理應該更早開始,或者需要調整周期,讓它運行得更快(如,增加后臺處理的線程數)。
2、晉升失敗
(to-space exhausted或者to-space overflow)
G1收集器完成了標記階段,開始啟動混合式垃圾回收,清理老年代的分區,不過,老年代空間在垃圾回收釋放出足夠內存之前就會被耗盡。(G1在進行GC的時候沒有足夠的內存供存活對象或晉升對象使用),由此觸發了Full GC。
下面日志中(可以在日志中看到(to-space exhausted)或者(to-space overflow)),反應的現象是混合式GC之后緊接著一次Full GC。
這種失敗通常意味著混合式收集需要更迅速的完成垃圾收集:每次新生代垃圾收集需要處理更多老年代的分區。
解決這種問題的方式是:
3、疏散失敗
(to-space exhausted或者to-space overflow)
進行新生代垃圾收集是,Survivor空間和老年代中沒有足夠的空間容納所有的幸存對象。這種情形在GC日志中通常是:
這條日志表明堆已經幾乎完全用盡或者碎片化了。G1收集器會嘗試修復這一失敗,但可以預期,結果會更加惡化:G1收集器會轉而使用Full GC。
解決這種問題的方式是:
4、巨型對象分配失敗
當巨型對象找不到合適的空間進行分配時,就會啟動Full GC,來釋放空間。這種情況下,應該避免分配大量的巨型對象,增加內存或者增大-XX:G1HeapRegionSize,使巨型對象不再是巨型對象。
六、Humongous Objects and Humongous Allocations
For G1 GC, any object that is more than half a region size is considered a “Humongous object”. Such an object is allocated directly in the old generation into “Humongous regions”. These Humongous regions are a contiguous set of regions. StartsHumongous marks the start of the contiguous set and ContinuesHumongous marks the continuation of the set.
Before allocating any Humongous region, the marking threshold is checked, initiating a concurrent cycle, if necessary.
Dead Humongous objects are freed at the end of the marking cycle during the cleanup phase also during a full garbage collection cycle.
In-order to reduce copying overhead, the Humongous objects are not included in any evacuation pause. A full garbage collection cycle compacts Humongous objects in place.
Since each individual set of StartsHumongous and ContinuesHumongous regions contains just one humongous object, the space between the end of the humongous object and the end of the last region spanned by the object is unused. For objects that are just slightly larger than a multiple of the heap region size, this unused space can cause the heap to become fragmented.
If you see back-to-back concurrent cycles initiated due to Humongous allocations and if such allocations are fragmenting your old generation, please increase your -XX:G1HeapRegionSize such that previous Humongous objects are no longer Humongous and will follow the regular allocation path.
This solution can have side effects – increasing the region size reduces the number of regions available so you need to be careful and run extra set of tests to see whether or not you actually improved the throughput or latency of the application.
本小結摘自:
https://www.oracle.com/technetwork/articles/java/g1gc-1984535.html
https://plumbr.io/handbook/gc-tuning-in-practice/other-examples/humongous-allocations
七、常見調優參數
1、-XX:MaxGCPauseMillis=N,(默認200毫秒,與throughput收集器有所不同)
前面介紹過使用GC的最基本的參數:
-XX:+UseG1GC -Xmx32g -XX:MaxGCPauseMillis=200前面2個參數都好理解,后面這個MaxGCPauseMillis參數該怎么配置呢?這個參數從字面的意思上看,就是允許的GC最大的暫停時間。G1盡量確保每次GC暫停的時間都在設置的MaxGCPauseMillis范圍內。 那G1是如何做到最大暫停時間的呢?這涉及到另一個概念,CSet(collection set)。它的意思是在一次垃圾收集器中被收集的區域集合。
- Young GC:選定所有新生代里的region。通過控制新生代的region個數來控制young GC的開銷。
- Mixed GC:選定所有新生代里的region,外加根據global concurrent marking統計得出收集收益高的若干老年代region。在用戶指定的開銷目標范圍內盡可能選擇收益高的老年代region。
在理解了這些后,我們再設置最大暫停時間就好辦了。 首先,我們能容忍的最大暫停時間是有一個限度的,我們需要在這個限度范圍內設置。但是應該設置的值是多少呢?我們需要在吞吐量跟MaxGCPauseMillis之間做一個平衡。如果MaxGCPauseMillis設置的過小,那么GC就會頻繁,吞吐量就會下降。如果MaxGCPauseMillis設置的過大,應用程序暫停時間就會變長。G1的默認暫停時間是200毫秒,我們可以從這里入手,調整合適的時間。
2、其他調優參數
-XX:G1HeapRegionSize=n
設置的 G1 區域的大小。值是 2 的冪,范圍是 1 MB 到 32 MB 之間。目標是根據最小的 Java 堆大小劃分出約 2048 個區域。
-XX:ParallelGCThreads=n(調整G1垃圾收集的后臺線程數)
設置 STW 工作線程數的值。將 n 的值設置為邏輯處理器的數量。n 的值與邏輯處理器的數量相同,最多為 8。
如果邏輯處理器不止八個,則將 n 的值設置為邏輯處理器數的 5/8 左右。這適用于大多數情況,除非是較大的 SPARC 系統,其中 n 的值可以是邏輯處理器數的 5/16 左右。
-XX:ConcGCThreads=n(調整G1垃圾收集的后臺線程數)
設置并行標記的線程數。將 n 設置為并行垃圾回收線程數 (ParallelGCThreads) 的 1/4 左右。
-XX:InitiatingHeapOccupancyPercent=45(調整G1垃圾收集運行頻率)
設置觸發標記周期的 Java 堆占用率閾值。默認占用率是整個 Java 堆的 45%。
該值設置太高:會陷入Full GC泥潭之中,因為并發階段沒有足夠的時間在剩下的堆空間被填滿之前完成垃圾收集。
如果該值設置太小:應用程序又會以超過實際需要的節奏進行大量的后臺處理。
避免使用以下參數:
避免使用 -Xmn 選項或 -XX:NewRatio 等其他相關選項顯式設置年輕代大小。固定年輕代的大小會覆蓋暫停時間目標。
-XX:G1MixedGCLiveThresholdPercent=65
為混合垃圾回收周期中要包括的舊區域設置占用率閾值。默認占用率為 65%。這是一個實驗性的標志。有關示例,請參見“如何解鎖實驗性虛擬機標志”。此設置取代了 -XX:G1OldCSetRegionLiveThresholdPercent 設置。Java HotSpot VM build 23 中沒有此設置。
-XX:G1MixedGCCountTarget=8
設置標記周期完成后,對存活數據上限為 G1MixedGCLIveThresholdPercent 的舊區域執行混合垃圾回收的目標次數。默認值是 8 次混合垃圾回收。混合回收的目標是要控制在此目標次數以內。Java HotSpot VM build 23 中沒有此設置。
-XX:G1OldCSetRegionThresholdPercent=10
設置混合垃圾回收期間要回收的最大舊區域數。默認值是 Java 堆的 10%。Java HotSpot VM build 23 中沒有此設置。
-XX:G1ReservePercent=10
設置作為空閑空間的預留內存百分比,以降低目標空間溢出的風險。默認值是 10%。增加或減少百分比時,請確保對總的 Java 堆調整相同的量。Java HotSpot VM build 23 中沒有此設置。
-XX:G1HeapWastePercent=10
設置您愿意浪費的堆百分比。如果可回收百分比小于堆廢物百分比,Java HotSpot VM 不會啟動混合垃圾回收周期。默認值是 10%。Java HotSpot VM build 23 中沒有此設置。
八、趨勢
其實可以看到Java 垃圾回收器的趨勢,就是在大內存堆的前提下盡 GC 可能的降低對應用程序的影響;從 CMS 的分階段增量標記,到 G1 通過 SATB 算法改正 remark 階段的 Stop The World 的影響,再到 Z/C4甚至在標記階段無需 Stop The World,莫不如此。
即強調Throughput、Latency,降低對于Footprint的要求。
Java ZGC: A Scalable Low-Latency Garbage Collector
九、細節
1、G1 mixed GC時機?
mixed gc中也有一個閾值參數 -XX:InitiatingHeapOccupancyPercent,當老年代大小占整個堆大小百分比達到該閾值時,會觸發一次mixed gc.
在分配humongous object之前先檢查是否超過 initiating heap occupancy percent, 如果超過的話,就啟動global concurrent marking,為的是提早回收,防止 evacuation failures 和 full GC。
為了減少連續H-objs分配對GC的影響,需要把大對象變為普通的對象,建議增大Region size。
一個Region的大小可以通過參數-XX:G1HeapRegionSize設定,取值范圍從1M到32M,且是2的指數。
2、XX:G1 HeapRegionSize 默認值?
默認把堆內存按照2048份均分,最后得到一個合理的大小。
3、直接內存配置
Q: 什么時候用直接內存?
A: 讀寫頻繁的場合,出于性能考慮,可以考慮使用直接內存。
直接內存也是 Java 程序中非常重要的組成部分,特別是 NIO 被廣泛使用之后,直接內存可以跳過 Java 堆,使 Java 程序可以直接訪問原生堆空間。因此可以在一定程度上加快內存的訪問速度。直接內存可以用 -XX:MaxDirectMemorySize 設置,默認值為最大堆空間,也就是 -Xmx。當直接內存達到最大值的時候,也會觸發垃圾回收,如果垃圾回收不能有效釋放空間,直接內存溢出依然會引起系統的 OOM。
一般而言直接內存在訪問讀寫上直接內存有較大優勢(速度較快),但是在內存空間申請的時候,直接內存毫無優勢而言。
十、其它
1、JDK 12 對于G1改進
Abortable Mixed Collections for G1
Promptly Return Unused Committed Memory from G1
推薦文檔:https://github.com/jiankunking/books-recommendation/blob/master/GC/G1/Java 9 - Garbage Collection.pdf
本文整理自:《實戰JAVA虛擬機 JVM故障診斷與性能優化》
參考:http://www.cnblogs.com/duanxz/p/6102580.html
個人微信公眾號:
作者:jiankunking 出處:http://blog.csdn.net/jiankunking
總結
以上是生活随笔為你收集整理的Java GC G1 详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: oracle 导入1t dmp文件,利用
- 下一篇: 前端三剑客——CSS