一次 JVM FullGC 的排查過程及解決方案!

本文來源:字節觀,是二阿公同窗的投稿

問題產生java

最近新上線的系統偶爾會報FullGC時間過長(>1s)的告警,查看GC日誌,以下圖所示:bash

看到GC日誌,我第一時間關注到的不是GC耗時,而是GC觸發的緣由:Metadata GC Threshold數據結構

也就是 FullGC 觸發的緣由是由於Metaspace大小達到了GC閾值。在監控系統裏面看了一下Metaspace的大小變化趨勢,以下圖所示:架構

按照以往的經驗,Metaspace在系統穩定運行一段時間後佔用空間應該比較穩定纔對,可是從上圖來看,Metaspace顯然是呈現大幅波動。爲何呢? 框架

相關知識
性能

咱們知道Metaspace主要存儲類的元數據,好比咱們加載了一個類,那麼這個類的信息就會按照必定的數據結構存儲在Metaspace中。學習

Metaspace的大小和加載類的數目有很大關係,加載的類越多,Metaspace佔用內存也就越大。spa

Metaspace被分配於堆外空間,默認最大空間只受限於系統物理內存。跟它相關的比較重要的兩個JVM參數:日誌

-XX:MetaspaceSize -XX:MaxMetaspaceSize複製代碼

MaxMetaspaceSize,你們從名字也能猜到是指Metaspace最大值。code

而MetaspaceSize可能就比較容易讓人誤解爲是Metaspace的最小值,其實它是指Metaspace擴容時觸發FullGC的初始化閾值。

在GC後Metaspace會被動態調整:若是本次GC釋放了大量空間,那麼就適當下降該值,若是釋放的空間較小則適當提升該值,固然它的值不會大於MaxMetaspaceSize.

另一個相關知識是:Metaspace中的類須要知足什麼條件纔可以被當成垃圾被卸載回收?

條件仍是比較嚴苛的,需同時知足以下三個條件的類纔會被卸載:

  1. 該類全部的實例都已經被回收;

  2. 加載該類的ClassLoader已經被回收;

  3. 該類對應的java.lang.Class對象沒有任何地方被引用。

排查過程

咱們能夠回過頭再細看GC日誌,能夠看出Metaspace已使用內存在FullGC後明顯變小(372620K -> 158348K),說明Metaspace通過FGC後卸載了不少類。

從這點來看,咱們有理由懷疑係統可能在頻繁地生成大量」一次性「的類,致使Metaspace所佔用空間不斷增加,增加到GC閾值後觸發FGC。

那麼這些被回收的類是什麼呢?

爲了弄清楚這點,我增長了以下兩個JVM啓動參數來觀察類的加載、卸載信息:

-XX:TraceClassLoading -XX:TraceClassUnloading複製代碼

加了這兩個參數後,系統跑了一段時間,從Tomcat的catalina.out日誌中發現大量以下的日誌:

到此基本能夠肯定Metaspace增加的元兇是這些類,那麼這些類

sun.reflect.GeneratedSerializationConstructorAccessorXXX

是幹嗎的呢?又是從哪裏引進來的呢?我也是一臉懵逼~~ 

根據類名Google了一把,找到了@寒泉子寫的《從一塊兒GC血案談到反射原理》,這篇文章對這些類的來源解釋得很透徹。在這裏我簡單總結以下:

Method method = XXX.class.getDeclaredMethod(xx,xx);method.invoke(target,params);複製代碼

這些類的來源是反射,相似上面所示的反射代碼應該你們都寫過或者看過,咱們經常使用的大多數框架好比Spring、Dubbo等都大量使用反射。

出於性能的考慮,JVM會在反射代碼執行必定次數後,經過動態生成一些類來將」反射調用」變爲「非反射調用」,以達到性能更好。而這些動態生成的類的實例是經過軟引用SoftReference來引用的。

咱們知道,一個對象只有軟引用SoftReference,若是內存空間不足,就會回收這些對象的內存;若是內存空間足夠,垃圾回收器不會回收它。只要垃圾回收器沒有回收它,該對象就能夠被使用。

那麼,究竟在何時會被回收呢?

SoftReference中有一個全局變量clock表明最後一次GC的時間點,有一個屬性timestamp,每次訪問SoftReference時,會將timestamp其設置爲clock值。

當GC發生時,如下幾個因素影響SoftReference引用的對象是否被回收:

  1. SoftReference對象實例多久未訪問,經過clock - timestamp得出對象大概有多久未訪問;

  2. 內存空閒空間的大小;

  3. SoftRefLRUPolicyMSPerMB常量值;

是否保留SoftReference引用對象的判斷參考表達式,true爲不回收,false爲回收:

clock - timestamp <= freespace * SoftRefLRUPolicyMSPerMB複製代碼

說明:

  • clock - timestamp:最後一次GC時間和SoftReference對象實例timestamp的屬性的差。就是這個SoftReference引用對象大概有多久未訪問過了

  • freespace:JVMHeap中空閒空間大小,單位爲MB。

  • SoftRefLRUPolicyMSPerMB:每1M空閒空間可保持的SoftReference對象生存的時長(單位ms)。這個參數就是一個常量,默認值1000,能夠經過參數:-XX:SoftRefLRUPolicyMSPerMB進行設置。

查看了一下咱們系統的JVM參數配置,發現咱們把SoftRefLRUPolicyMSPerMB設置爲0了,這樣就致使軟引用對象很快就被回收了。進而致使須要頻繁從新生成這些動態類。

爲了驗證這個猜想,我把SoftRefLRUPolicyMSPerMB改爲了6000進行觀察,發現果真猜得沒錯。

系統啓動後不久Metaspace的使用空間基本保持不變了,運行幾天後也沒再出現由於Metaspace大小達到閾值而觸發FGC。至此問題解決。

References

假笨說-從一塊兒GC血案談到反射原理:

https://mp.weixin.qq.com/s/5H6UHcP6kvR2X5hTj_SBjA?

Java的強引用,軟引用,弱引用,虛引用及其使用場景:

http://blogxin.cn/2017/09/16/java-reference/

END

我的公衆號:石杉的架構筆記(ID:shishan100)

歡迎長按下圖關注公衆號:石杉的架構筆記!

公衆號後臺回覆資料,獲取做者獨家祕製學習資料

石杉的架構筆記,BAT架構經驗傾囊相授

相關文章
相關標籤/搜索