记录一次大对象导致的Java堆内存溢出问题
問題描述
前幾天早上出現一后臺項目無法登陸的情況,排查發現新生代和老年代都占用100%,FullGC次數大概有100多次,最終出現OOM。
重啟Tomcat后,至13點,FullGC的次數達到31次。
排查過程
可知,新生代中Survivor占51.2MB,無法放入127MB的char[]實例。
故這種對象如果在第一次MinorGC時存活,它將無法進入survivor,而會提前轉移到老年代。
4. 那么,這類大小為127MB的局部變量為什么在MajorGC時能夠存活?推測原因如下:
(1)第3點所述的熬過一輪MinorGC提前進入老年代的對象不斷增加,直至占滿老年代的70%。
(2)這時由于CMSInitiatingOccupancyFraction=70,將觸發CMS的MajorGC。
(3)我們知道CMS的GC有部分過程是可以與用戶線程同時執行的,假如在這個過程中,用戶線程產生的對象大小占滿老年代剩余的30%,那么CMS并發模式的GC就失敗了(concurrent mode failure)。
(4)當CMS的并發GC失敗后,將使用Serial Old的串行GC重新執行。
(5)Serial Old的GC是會全過程Stop The World的,也就是造成長時間停頓。
5. 為了驗證上述結論,開啟GC日志后對此場景進行復現。
復現記錄
(1)MC:方法區大小。按目前使用量,可調整為75MB。
(2)CCSC:壓縮類空間大小。按目前使用量,可調整為10MB。
可以發現Eden增加65MB,老年代增加200MB。
4. 重復多次請求接口后,新生代、老年代均被占滿。
5.此時關閉所有頁面,等待10分鐘左右,JVM占滿情況仍然無法恢復(Eden和survivor區偶爾會出現減少,但馬上又會被迅速占滿,old區始終維持占滿狀態)。
6.清理cookie后,重新登錄,出現與之前情況一致的無法登錄的現象。
7.執行dump:live,得到7.9G文件。(此處與上次情況不同,上次執行完后,old區域就被回收掉了,而dump文件也只有142M。另外,上次tomcat日志中有出現OOM的日志,本次沒有)
8.重啟該tomcat,截止重啟前,GC情況如下:
GC分析
GC日志
1.第一次出現Full GC (Allocation Failure)在1544.618。
2.伴隨出現concurrent mode failure,這種提示代表無法在老年代填滿之前完成垃圾回收,或者一個新的對象無法在老年代的剩余空間完成分配,這時程序會停止所有線程來完成GC。原文如下:
if the concurrent collector is unable to finish reclaiming the unreachable objects before the tenured generation fills up, or if an allocation cannot be satisfied with the available free space blocks in the tenured generation, then the application is paused and the collection is completed with all the application threads stopped
3.結束時間為2723.551,即從老年代占滿到被重啟間隔1179秒,約20分鐘。
堆分析
下圖為復現過程的dump文件,大小最大的已經不是char[],不過前幾個過大的對象均為調用上述接口中的局部變量。
解決辦法
1.優化JVM啟動參數
(1)調整堆內存初始值為-Xms5120m避免擴容
(2)調整新生代大小為-Xmn1536m
(3)CMSInitiatingOccupancyFraction=60
(4)方法區大小調整為100MB
(5)壓縮類空間大小調整為15MB
2.對該接口實現進行優化
3.JVM參數調整后跟蹤FullGC情況
總結
以上是生活随笔為你收集整理的记录一次大对象导致的Java堆内存溢出问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Presto日志中出现大量的Trigge
- 下一篇: 数据仓库与联机分析处理笔记