聊聊實際業務開發中容易觸發FullGC的場景
對于我們程序員來講,FullGC是我們既陌生又熟悉的老伙伴,如果它發生的不頻繁的話我們是不需要處理,如果它發生的很頻繁我們就需要及時的處理,不然會影響線上用戶的正常功能使用。下面我們一些常見的容易觸發FullGC場景。
1、實際業務中FullGC的場景分析
每當服務器接受到請求之后,服務器需要構造的各種各樣的不同的業務對象,與此同時,在實際的業務開發中,我們創建的業務對象很可能是一個大對象(如果對象是個大對象,那么對象就在老年代中存儲),如下圖所示:
圖片
如果此時我們創建好業務對象之后,還需要去調用其他的服務獲取業務,假設這個時候調用的其他服務超時了(服務C超時),如下圖所示:
圖片
那么一旦發生這種情況,那么此時我們創建的業務對象就會常駐在我們JVM內存里面,如果我們的超時時間是15秒,那么在這個15秒里面我們這個系統里面的業務對象在內存里面一直常駐。如果在高并發下出現這樣的情況,那么將會有很多的業務對象常駐在內存中,如下圖所示:
圖片
此時在JVM中的就會出現年輕代的對象不斷的轉移到老年代中,如下圖所示:
圖片
由于老年代的中不斷又對象添加,最終會導致老年代中由于內存不足,無法在開辟空間存在其他的業務對象,進而觸發FullGC。
2、整理常見的發生FullGC的原因
FullGC是對老年代和新生代同時進行回收,通常還會伴隨對元空間(或永久代)的回收,會導致機器停頓,影響線上服務。
(1)老年代空間不足
當對象需要被分配到老年代,但老年代剩余空間無法滿足需求時,就會觸發FullGC。長期存活的對象進入老年代,導致老年代的空間不足。
JVM給每個對象定義了一個年齡計數器(在對象頭中記錄)。對象在Eden區誕生,經過一次YoungGC后存活,就會被移動到Survivor 區,并且年齡加1。當它的年齡增長到一定程度(默認為15,也可以通過參數設置),下一次Young GC時它就會被晉升到老年代。如果此時老年代空間不足,就會觸發Full GC。
(2)空間分配擔保失敗
當要準備觸發一次YoungGC時,會進行空間分配擔保,在擔保中,假如虛擬機檢查出老年代最大可用連續空間小于新生代所有對象的總空間,但是HandlePromotinotallow=false,那么就會進行一次FullGC(HandlePromotionFailure這個配置在jdk7中不支持了,這一步驟在jdk7以后版本已取消)。
當要準備觸發一次YoungGC時,會進行空間分配擔保,再擔保過程中,發現虛擬機會檢查老年代最大可用的連續空間小于新生代所有對象的總空間,但是HandlePromotinotallow=true,繼續檢查發現老年代最大可用連續空間小于歷次晉升到老年代的對象平均大小時,會觸發一次FullGC。
(3)調用 System.gc()方法
在代碼中直接調用System.gc()或Runtime.getRuntime().gc()會建議 JVM 進行 FullGC。這里需要注意的點是只是建議,并非強制,但大多數情況下JVM都會執行。為了避免開發人員濫用,通常我們會通過參數-XX:+DisableExplicitGC來禁止這種顯式的GC。
(4)內存泄漏
雖然老年代總空間很大,但如果存在內存泄漏,一些“垃圾對象”因為被意外的引用持有而無法被回收(典型的是ThreadLocal使用不當就會出現內存泄漏的問題)。每次YoungGC后,都有一批本應被回收的對象因為泄漏而被迫進入老年代,最終導致老年代中使用部分達到回收閾值,觸發FullGC。
(5)老年代碎片化嚴重
如果YoungGC后Survivor區存活對象正常的晉升到老年代,但老年代也因為碎片化嚴重而無法容納這些對象,即使總的剩余空間還很多,也會觸發FullGC來整理碎片。
偶爾一次FullGC沒問題,但是頻繁的FullGC或者FullGC的時間很長,這些問題都是需要我們關注的。
3、實際業務中常見的易導致FullGC場景
(1)雪崩效應
在系統中依賴其他服務返回的響應數據,如果其他服務返回響應超時,容易導致當前內存中的數據激增而導致觸發FullGC。
(2)大對象分配
文件處理是常見業務場景,處理文件解析時候,如果用戶上傳一個上百兆的大文件,此時服務端一次性將其讀入內存數組中,那么這個數組就是一個大對象,很可能直接在老年代分配。如果此時有多個用戶同時上傳大文件,就會迅速擠占老年代空間,進而導致觸發FullGC。
(3)內存泄漏
一個后臺任務系統,需要緩存一些任務執行上下文,開發人中常見的處理方案是將這些上下文對象放入了一個靜態的HashMap中(或者ThreadLocal)。由于靜態集合的生命周期與類加載器相同(通常就是程序運行期間),這些上下文對象永遠無法被回收。隨著系統運行,那么這個HashMap越來越大,最終老年代內存不足,觸發頻繁的FullGC。
(4)資源未關閉
數據庫連接、文件流等資源未及時關閉雖然不是嚴格的內存泄漏,但如果大量連接或流對象因為異常等原因沒有被正確關閉,它們所關聯的Java對象和native memory就無法被及時釋放,也會導致內存壓力激增,觸發頻繁的FullGC。































