前言
Full GC相對於Minor GC來說,停止用戶線程的STW(stop the world)時間過長,至少慢10倍以上,所以要盡量避免,首先說一下Full GC可能產生的原因,接着給出排查方法以及解決策略。
Full GC產生原因
下圖為與產生Full GC相關的內存區域,初生代、老年代、以及Metaspace區域。
System.gc()方法的調用
在代碼中調用System.gc()方法會建議JVM進行Full GC,但是注意這只是建議,JVM執行不執行是另外一回事兒,不過在大多數情況下會增加Full GC的次數,導致系統性能下降,一般建議不要手動進行此方法的調用,可以通過-XX:+ DisableExplicitGC來禁止RMI調用System.gc。
老年代(Tenured Gen)空間不足
在Survivor區域的對象滿足晉升到老年代的條件時,晉升進入老年代的對象大小大於老年代的可用內存,這個時候會觸發Full GC。
Metaspace區內存達到閾值
從JDK8開始,永久代(PermGen)的概念被廢棄掉了,取而代之的是一個稱為Metaspace的存儲空間。Metaspace使用的是本地內存,而不是堆內存,也就是說在默認情況下Metaspace的大小只與本地內存大小有關。-XX:MetaspaceSize=21810376B(約為20.8MB)超過這個值就會引發Full GC,這個值不是固定的,是會隨着JVM的運行進行動態調整的,與此相關的參數還有多個,詳細情況請參考這篇文章jdk8 Metaspace 調優
統計得到的Minor GC晉升到舊生代的平均大小大於老年代的剩余空間
Survivor區域對象晉升到老年代有兩種情況:
- 一種是給每個對象定義一個對象計數器,如果對象在Eden區域出生,並且經過了第一次GC,那么就將他的年齡設置為1,在Survivor區域的對象每熬過一次GC,年齡計數器加一,等到到達默認值15時,就會被移動到老年代中,默認值可以通過-XX:MaxTenuringThreshold來設置。
- 另外一種情況是如果JVM發現Survivor區域中的相同年齡的對象占到所有對象的一半以上時,就會將大於這個年齡的對象移動到老年代,在這批對象在統計后發現可以晉升到老年代,但是發現老年代沒有足夠的空間來放置這些對象,這就會引起Full GC。
堆中產生大對象超過閾值
這個參數可以通過-XX:PretenureSizeThreshold進行設定,大對象或者長期存活的對象進入老年代,典型的大對象就是很長的字符串或者數組,它們在被創建后會直接進入老年代,雖然可能新生代中的Eden區域可以放置這個對象,在要放置的時候JVM如果發現老年代的空間不足時,會觸發GC。
老年代連續空間不足
JVM如果判斷老年代沒有做足夠的連續空間來放置大對象,那么就會引起Full GC,例如老年代可用空間大小為200K,但不是連續的,連續內存只要100K,而晉升到老年代的對象大小為120K,由於120>100的連續空間,所以就會觸發Full GC。
CMS GC時出現promotion failed和concurrent mode failure
這個原因引發的Full GC可以參考這篇文章,下面也摘抄自這篇文章:JVM 調優 —— GC 長時間停頓問題及解決方法
- 提升失敗(promotion failed),在 Minor GC 過程中,Survivor Unused 可能不足以容納 Eden 和另一個 Survivor 中的存活對象, 那么多余的將被移到老年代, 稱為過早提升(Premature Promotion)。 這會導致老年代中短期存活對象的增長, 可能會引發嚴重的性能問題。 再進一步, 如果老年代滿了, Minor GC 后會進行 Full GC, 這將導致遍歷整個堆, 稱為提升失敗(Promotion Failure)。
- 在 CMS 啟動過程中,新生代提升速度過快,老年代收集速度趕不上新生代提升速度。在 CMS 啟動過程中,老年代碎片化嚴重,無法容納新生代提升上來的大對象,這是因為CMS采用標記清理,會產生連續空間不足的情況,這也是CMS的缺點
總結
可以發現其實堆內存的Full GC一般都是兩個原因引起的,要么是老年代內存過小,要么是老年代連續內存過小。無非是這兩點,而元數據區Metaspace引發的Full GC可能是閾值引起的,詳細原因還是建議參考其他文章,我就不誤人子弟了。
檢測JVM堆的情況
- 可以使用JDK的bin目錄下的jvisualvm.exe工具來進行實時監測,這個是圖形化界面,最為直觀,這是一個強大的工具。
- 采用jps找到進行id,然后使用jstat -gc pid來實時進行檢測。
- 運行程序前設置-XX:+PrintGCDetails,-XX:+PrintGCDateStamps參數打印GC的詳細信息進行分析。
解決策略
如果是發現由於老年代內存過小頻繁引起的Full GC,那么可以適當增加老年代的內存大小,如果是發現是由於老年代沒有連續空間來讓初生代的對象晉升,如果是采用CMS,那么可以設置進行 n 次 CMS 后進行一次壓縮式 Full GC,參數如下:
-XX:+UseCMSCompactAtFullCollection:允許在 Full GC 時,啟用壓縮式 GC
-XX:CMSFullGCBeforeCompaction=n 在進行 n 次,CMS 后,進行一次壓縮的 Full GC,用以減少 CMS 產生的碎片。
除此之外,盡量少創建大對象,不要在代碼里調用System.gc(),什么時候進行Full GC這種事情還是交給JVM來做。在讀取文件后記得釋放資源,不要讓JVM無法回收垃圾,造成內存泄漏。
本文如果有哪些地方不對,或者有可以補充的地方希望您可以指出來,謝謝了。
參考文獻:
https://blog.csdn.net/chenleixing/article/details/46706039
https://blog.csdn.net/YHYR_YCY/article/details/52566105