一、序言
目前企業級主流使用的Java版本是8,垃圾回收器支援手動修改為G1,G1垃圾回收器
是Java 11的預設設定,因此G1垃圾回收器可以用很長時間,現階段垃圾回收器優化意味著針對G1垃圾回收器優化。
為了簡化討論,下面假設針對4C/16G
物理機器進行優化。
二、G1概覽
(一)瞭解G1
1、最大堆大小
G1管理的最大堆大小為64G。每個Region的大小通過-XX:G1HeapRegionSize
來設定,大小為1~32MB
,預設最多可以有2048個Region,G1能管理的最大堆記憶體是32MB*2048=64G
。
使用G1垃圾回收器最小堆記憶體應為1MB*2048=2GB
,低於此值建議使用其它垃圾回收器。
2、Region大小
Region大小為1~32MB
,具體取值有1MB、2MB、4MB、8MB、16MB、32MB,Region大小優化與大物件有關,當物件佔用記憶體超過Region的一半時將被視為大物件。
被標記為大物件將不利於垃圾回收。
3、獲取預設值
檢視本地JVM特別是G1垃圾回收器當前的預設值。
java -XX:+PrintFlagsInitial >> ~/1.txt
(二)三種GC模式
G1垃圾回收器有兩種垃圾回收模式,新生代回收
和混合回收
,特殊情況下會切換到Full GC
。
1、新生代回收
新生代回收在最大停頓時間內,會處理所有Eden區的垃圾。具體操作是將Eden區所有存活的物件複製到Survivor區,同時清空Eden區。
新生代回收伴隨著應用暫停
,最長停頓時間不超過最大停頓時間
,新生代回收儘管有暫停機制,考慮到並行回收的特性,回收邏輯相對簡單,回收效率依然較高。一般而言,新生代回收實際耗時通常低於最大停頓時間。
新生代回收觸發時機是新建立的物件在Eden區找不到足夠的儲存空間。
2、混合回收
混合回收伴隨著新生代回收和老年代回收,在最大停頓時間範圍內,會處理大部分Eden區的垃圾和一部分老年代垃圾。
老年代回收毫無疑問會伴隨著應用暫停。混合回收操作比較複雜,相對新生代回收來說,單位時間回收的垃圾數要少,回收效率要低。一般而言,混合回收的實際耗時通常接近或者等於最大停頓時間。
混合回收觸發時機是由引數InitiatingHeapOccupancyPercent
控制,預設值為45,含義是老年代佔用空間大小與堆的總大小比值超過此數便會觸發混合回收。
預設值45%
是比較合理的,不建議所謂的調優。老年代回收策略同樣是將選定Region區記憶體活的物件複製到空閒Region區,混合回收伴隨著回收新生代垃圾能夠清理出更大的空閒Region區來存放老年區存活物件,保證回收過程能夠正常進行。
老年區存活物件一般較多,物件在記憶體中複製耗時較長,因此相對來說混合回收效率較低。
3、Full GC
Full GC是所有G1垃圾回收調優者盡力迴避的情況,單執行緒回收垃圾,回收物件是整個堆,不再受最長停頓時間約束,一旦出現此情況,意味著應用的響應時間無情的變長。
當應用不定期進入Full GC
狀態時,與其任由其單執行緒重塑堆記憶體,不如採用冗餘策略,在流量低谷時刻,逐一重啟應用,主動重塑堆記憶體空間。
流量高峰期出現Full GC現象及其應對策略後面再討論。
(三)預設引數
1、堆記憶體
引數 | 預設值 | 說明 | 優化建議 |
---|---|---|---|
MaxGCPauseMillis | 200ms | 最大停頓時間 | |
G1HeapRegionSize | 不設定時啟發式推斷 | ||
G1NewSizePercent | 5 | 新生代最小百分比 | |
G1MaxNewSizePercent | 60 | 新生代最大百分比 |
2、新生代記憶體回收
引數 | 預設值 | 說明 | 優化建議 |
---|---|---|---|
ParallelGCThreads | 並行GC執行緒數,會根據CPU核數推斷 | 預設值 | |
MaxTenuringThreshold | 15 | 從新生代晉升到老年代年齡閾值 | |
SurvivorRatio | 8 | Eden和一個Survivor的比例 | |
TargetSurvivorRatio | 50 | Survivor區記憶體使用率,增大該值會降低到老年代概率 | |
+G1EagerReclaimHumongousObjects | true | 是否在YGC時回收大物件 |
3、混合回收
引數 | 預設值 | 說明 | 優化建議 |
---|---|---|---|
G1MixedGCCountTarget | 8 | 值越大,收集老年代分割槽越少 | |
G1OldCSetRegionThresholdPercent | 10 | 表示一次最多收集10%的分割槽 |
三、垃圾在堆中流轉
垃圾回收器調優的關鍵是儘可能減少Mixed GC的頻率,換句話說盡可能減少垃圾流轉到老年代
。GC調優便是認識垃圾在堆中的流轉規律,從而對流向老年代的垃圾予以提前干涉,使之儘可能留在新生代
。
垃圾在新生代(主要指Eden區)中,垃圾回收使用YGC,回收執行緒與應用執行緒併發進行,垃圾回收對應用透明進行,假如CPU算力充足的話,應用幾乎感覺不到垃圾在回收進行。
垃圾在老年代中,垃圾回收採用Mixed GC,回收執行緒開始工作時,應用執行緒阻塞,等待回收執行緒工作完畢有,應用執行緒重新被喚醒。頻繁的Mixed GC對應用的吞吐量產生不良影響。
1、物件如何進入老年代
一般而言,新建立的物件會存在於新生代的Eden區,下一次垃圾回收處罰便直接回收了。如果物件比較頑強(繼續被其它物件引用),那麼會在Survivor區流轉,每GC一次,仍然不能被垃圾回收,那麼年齡加一,繼續在S0和S1區流轉,當年齡增長到一定的閾值,直接進入老年代。
(1)大物件直接到老年代
新建立的物件如果過大,那麼不經過新生代,直接進入老年代。控制物件大小閾值有引數-XX:PretenureSizeThreshold
決定,單位位元組
。
(2)動態年齡判斷
除了物件在S0和S1區反覆流轉年齡變化外,垃圾回收維護另外一套獨立的年齡判定規則:如果YGC後尚未被回收的垃圾超過了Survivor區的50%,那麼超過的這批物件會直接進入老年代。
12G * 60% * 10% * 50% * 1024 = 737MB
動態年齡判定規則要求每次YGC儘可能的徹底,意味著每次GC的最長時間不能太短,預設200毫秒是比較合理的值。
如果預設定的最長停頓時間過短,那麼每次GC後存活大量尚未被回收的垃圾,S區容量有限,不該進入老年代的垃圾快速在老年代堆積,頻繁的Mixed GC不可避免。
2、高併發加速進入老年代
在高併發場景下,CPU和記憶體資源吃緊,負載很高,不確定的效能抖動加速垃圾進入老年代。
舉例說明,DAO層查詢資料庫,一次完整的會話結束後,整個會話中產生的物件垃圾在Eden區應當被全部回收。由於網路波動,資料庫處理能力的限制,大量會話超時。在此過程中這部分物件垃圾很可能在快速S0和S1流轉中疊加年齡,或者觸發動態年齡判定,直接進入老年代。
老年代記憶體空間不夠用,觸發Mixed GC,Mixed GC直接副作用是應用卡頓。
四、調優步驟
1、設定垃圾回收器
Java 8需要手動指定G1垃圾回收器,命令列新增-XX:+UseG1GC
引數。
2、設定堆大小
設定記憶體堆大小有兩點需要注意:初始堆大小與最大堆大小保持一致;堆大小佔實體記憶體大小75%~80%
,給系統核心服務預留必要的記憶體。
引數-Xmx12G
設定初始堆大小;引數-Xms12G
設定最大堆大小。
3、元空間設定
元空間是指儲存靜態類、靜態方法、常量等特殊變數的記憶體區域。
引數-XX:MetaspaceSize=1G
設定元空間初始大小;引數-XX:MaxMetaspaceSize=1G
設定元空間最大大小。
4、GC停頓時間
GC停頓時間
是指每次YGC
或者Mixed GC
的最大時間,垃圾回收器會根據使用者設定的期望時間動態選擇垃圾掃描的範圍,如果設定時間過小,可能總有一部分垃圾不能得到回收。單位毫秒
。
-XX:MaxGCPauseMillis=200
5、新生代大小
引數-XX:G1NewSizePercent
設定新生代初始大小,預設為5%
;引數-XX:G1MaxNewSizePercent
設定新生代最大大小,預設為60%
。
新生代內部細化為 Eden
區和兩個 Survivor
,預設比例是: 8:1:1
Eden: 12G * 60%* 80% = 5.76G
S0: 12G * 60%* 10% = 0.72G
S1: 12G * 60%* 10% = 0.72G
假設併發系統每秒建立500MB的物件,假設每次YGC根據預先設定的最長停頓時間都能夠掃描到Eden Region,那麼此併發系統大約每隔10秒需要進行一次YGC。
五、調優實踐
GC垃圾回收調優是在物理硬體受限制,並且有調優的理論空間下進行的。條件允許的話,直接升級硬體配置特別是實體記憶體配置,能夠有效降低GC頻率。比如8C32G
或者16C64G
等。
1、頻繁的YGC
當併發量較大時,頻繁的YGC時必然的,單位時間類建立了更多的物件,使用完畢之後成為了垃圾。頻繁的YGC有加速S區物件流向老年代的可能,儘可能保證每次YGC的實際耗時低於預設定的最長垃圾回收時間(預設200毫秒),以便能夠每次都能將新生代垃圾清理完成,儘可能延緩垃圾流向老年代。
2、頻繁的Mixed GC
在G1垃圾回收器中,沒有所謂的Mixed GC的概念,Mixed GC類似於F·GC,不同的是Mixed GC除了回收老年代,同時也回收新生代,共同之處在於都會產生STW
。
頻繁的Mixed GC
本質是大量應該在新生代回收的垃圾進入了老年代,解決思路是排查哪些哪些垃圾(物件)應該留在新生代,卻流轉到老年代。
(1)大物件
檢查應用程式是否週期性的建立大物件,大物件的閾值由引數-XX:PretenureSizeThreshold
控制。假如記憶體有優化空間的前提下適當調高此值,不得超過S區的一半(似乎沒有這麼大的物件),副作用是新生代存放物件數量相應變少,Eden區記憶體更快的用完,YGC相應的變頻繁一些。
從業務的角度來講,大物件產生必有其產生的原因,從這個角度優化可能性不高,垃圾回收器優化儘可能遮蔽業務層程式碼,畢竟對開發提要求讓其不要建立大物件不現實。
(2)元空間
元空間耗盡也會引發Mixed GC
,考慮到元空間儲存內容的特殊性,因元空間耗盡導致GC頻率提高並沒有很好的辦法。單純提高元空間大小會壓縮新生代大小,新生代變小,物件流轉到老年代的數量會變多,老年代記憶體消耗加快,同樣會提高GC的頻率。
因元空間耗盡引發的Mixed GC
,相對來說增加實體記憶體是比較優的解決方式。
3、Full GC
儘管Mixed GC
被觸發時,應用會暫時停止響應(預設值是200毫秒),暫停的時間是相對可控的。
如果在進行Mixed GC
時,空閒的Region無法儲存存活的物件,Mixed GC無法正常進行時,垃圾回收會切換到 G1 之外的 Serial Old GC
來收集整個堆,包括新生代、老年代、元空間等。
進入Serial Old GC
垃圾回收狀態,垃圾回收不再受最長回收時間約束,採用單執行緒進行標記、清理和壓縮整理,應用可能進入假死
狀態。也許重啟應用,重新分配堆記憶體,將堆記憶體徹底洗牌,也許會更好。
G1垃圾回收調優的關鍵是不要出現Full GC,因此對於敏感的引數千萬不要亂調優,否則不僅達不到理想想過,反而更糟糕。