記一次 OutOfMemoryError: Java heap space 的排錯



1、情況概述

公司以前的某報名系統,項目啟動后,在經過用戶一段時間的使用之后,項目響應便開始變得極其緩慢,最后幾乎毫無反應。日志里輸出了一些似乎無關痛癢的異常,逐步修復,項目仍然出現這種情況,且 “項目啟動 -> 服務無響應” 這段時間並不穩定。直到在被反復折磨的這幾天里終於日志抓到了幾個異常,都是 javax.servlet.ServletException: java.lang.OutOfMemoryError: Java heap space

2、異常分析

JVM在啟動時默認設置可調配的內存空間為物理內存的1/64但小於1G,如果該空間的可用空間不足 2%,則拋出異常  OutOfMemoryError : Java heap space

項目中的日志模塊並沒能輸出可追溯的內存溢出,只能先排除一些猜想:
  • 項目中幾乎不涉及大圖片加載,流不關閉等情況可以排除
  • 項目中的對象模型並不復雜,JVM的初始參數足夠使用,所以單純調整JVM的參數設置不是個好辦法

在了解一下定位排錯的方式后,發現這么個東西:JVM Heap Dump(堆轉儲文件),Heap Dump 記錄了JVM中堆內存運行的情況,可以使用JDK提供的命令 jmap 生成,命令格式如下:
jmap -dump:live,format=b,file=heap-dump.bin <pid>

其中 pid 是JVM進程的id, heap-dump.bin 是生成的文件名稱,會在執行命令的目錄下生成該文件

注:在執行命令生成 dump 文件的過程中,曾報錯 "Insufficient memory or insufficient privileges to attach",這是因為權限問題,調用系統服務啟動的tomcat和命令行執行命令看上去都在同個administrator用戶下,其實不然。解決方法是將 tomcat 以 startup.bat 啟動,再在命令行調用 jmap 即可。

分析 dump 文件的工具也有不少,這里使用了很多人都推薦的 Eclipse Memory Analyzer(MAT),這是 Eclipse 提供的一款用於 Heap Dump 文件的工具,有插件的形式,也可以獨立運行。

使用該工具打開生成的 dump 文件,緩慢分析載入后彈出選框,選擇 Leak Suspects Report:

 Dominator Tree :支配樹,列出Heap Dump中處於活躍狀態中的最大的幾個對象,默認按 retained size進行排序,因此很容易找到占用內存最多的對象。

使用工具的支配樹功能,看到如下:

兩個最高占比,而奇怪的在於之中:

總占比 29.85%,但是之中最大的對象竟然也就只占了 2.41%,怎么回事?仔細一看,除開前幾個對象之外,后面全部都是 Examinee 對象,數量之多,下面的黑體提示 " Total: 25 of 228,841 entries; 228,816",剩余二十多萬個對象,展開一看全是 Examinee 和相關 Hibernate 的 EntityEntry 對象!那么造成內存溢出的問題就顯而易見了, 內存中加載的數據量過於龐大,可能是循環引用造成的內存泄漏,也可能是對象產出過快垃圾回收無法及時處理

3、錯誤定位

跟用戶進行溝通后,了解到其主要是在進行考生報名添加的操作,於是進行了模擬,發現在添加報名時發送了一個請求響應很慢,數據量也很大:

響應耗時2s,數據量竟然有2MB,點開一看,正是大量的考生信息數據,足足有30000+條:

看了下這個請求數據的作用,是在拿給前端的一個插件進行自動選擇用的:

再看后台的代碼也很粗暴,數據沒做處理,抓出來直接全丟給前台了,這個過程中當然也就生成了成千上萬個 Examinee 對象:

因為這個方法是在每次添加考生報名的時候都會觸發,而用戶在進行考生報名時添加操作很頻繁,如果多個用戶同時進行添加操作,那么短時間內產生的考生對象 Examinee 將直線上升,垃圾回收清理不及時,於是內存溢出的異常也就隨之而來了。

知道了因果,那么把前端的數據抓取方式改一下就解決了。

修改之前,系統異常時的 dump 文件足有 280MB,修改之后系統穩定運行,生成的 dump 文件大概只有 100MB 左右了,完結撒花。

4、回顧和經驗

這個項目前年就投入使用,去年也僅是增加了微信支付等和核心業務沒有太大關聯的相關功能,直到今年才暴露這個問題,其原因也正是因為數據量隨着時間在不斷增加,以往數據量小,哪怕數據完全加載也沒有壓力,但是現在完全加載的話,內存就吃不消了。

這也是為什么我之前使用 "項目重啟" 的方式來恢復使用,但每次的效果卻越來越差的原因,因為隨着使用數據量也越來越大了。

這次排錯的兩點收獲:
  • 學會了最基本的內存分析方式,通過 dump 文件和 MAT 工具
  • 明白了某些功能在生產運行的過程中,可能會隨着數據量和業務情況的不斷龐大而性能下降,在編寫代碼初期就要盡量預估將來數據量的發展趨勢,以做出穩定合理的算法

5、參考鏈接



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM