JAVA內存泄露分析及解決

,問題產生
    項目採用Tomcat6.0爲服務器,數據庫爲mysql5.1,數據庫持久層爲hibernate3.0,以springMVC3.0爲框架,項目開發完成後,上線前夕進行穩定性拷機,測試數據爲插入4條/S,更新4條/S,訪問300次/S,前期運行速度順暢,三天後就開始運行緩慢,訪問量達到1500W次後以拋出Java heap space結束.
.問題分析
    1.前期分析爲鏈接池內存溢出,期間優化了鏈接池參數,調整了tomcat線程參數,替換數據庫鏈接池,問題依舊
    2.看來問題不是簡單的參數配置,須要進行一點深刻的研究分析了,在和同事研究了一些資料後,對JAVA的垃圾收集機制有了必定的瞭解,JVM的內存模型分爲新生代和老生代,JDK自己提供了一個監視工具jconsole.exe,進入bin文件夾打開後,選擇鏈接--tomcat--內存,能夠開始監視JVM內存回收狀況,經過一段時間的監視後,發現了老生代的內存回收存在異常.

mysql

從上圖能夠看出,每次回收的內存都比上一次少,能夠判斷老生代的內存發生了泄露.
    3.雖然知道了存在內存泄露,可是沒法判斷是哪裏發生了泄露,爲此咱們須要把堆(dump)導出來進行分析,JDK也提供了導出工具jvisualvm.exe,啓動後右鍵點擊線程--堆Dump,能夠導出Dump文件.
    4.使用MAT(MemoryAnalyzer)分析Dump文件,該工具的下載地址爲:http://www.eclipse.org/mat,能夠下載離線版,也能夠集成到eclipse,使用很方便.打開該工具導入Dump文件,稍等一會,就能夠得出MAT提供的分析報告spring

MAT指出了該Dump中一個HashMap的實例發生了內存泄露,佔用了JVM819M的內存,繼續點擊Details能夠獲得更詳細的信息

sql

這個detail比較詳細的指出了問題所在,一個叫viewCache的hashMap實例佔用共859M內存,雖然每一個實例只有幾百字節,可是一共產生了134W個實例.
    5.分析出這個問題點,接下去就是排查代碼問題了,排查代碼得知該項目使用springMVC,其中viewCache是spring中使用的一個視圖緩存,在項目中如一個處理視圖跳轉的代碼:

數據庫

LinkedList list = this.getPathParam(mvValue);
    for (int i = 0; i < list.size(); i++) {
     String paramName = (String) list.get(i);
     String paramValue = null;
     paramValue = RequestUtils.getParamString(map, paramName);
     paramValue = paramValue == null ? "" : paramValue;
     mvValue = StringUtils.replace(mvValue, "#" + paramName+"#", paramValue);
   }
    return new ModelAndView(mvValue); 

因爲paramValue每次都是動態生成的uuid,形成了每次的mvvalue都不一樣,這樣每次都會生成一個不同的視圖,這樣的視圖累積到100多W個的時候,終於把tomcat撐暴了.百度了一下,果真也已經有人注意到了這個問題:http://jackyrong.iteye.com/blog/1744342.
    6.知道了問題所在後,就能夠修改代碼了,咱們須要將ModelAndView的視圖名稱固定,動態參數能夠經過ModelAndView提供的addObject方法傳入,修改後的代碼以下:緩存

      //判斷若是視圖名稱包含"snms_result.jsp",則將視圖名稱返回值統一,解決因動態產生視圖名稱過多產生的內存泄露問題, by feitianbubu 2013年5月27日 11:21:31
    ModelAndView mv=new ModelAndView(mvValue);
    LinkedList list = this.getPathParam(mvValue);
    for (int i = 0; i < list.size(); i++) {
     String paramName = (String) list.get(i);
     String paramValue = null;
     paramValue = RequestUtils.getParamString(map, paramName);
     paramValue = paramValue == null ? "" : paramValue;
     mv.addObject("id", ",'"+paramValue+"'");
    }
    return mv;

7. 代碼commit SVN,發佈拷機,觀察內存回收狀況

tomcat

(爲了更快模擬環境採用1000次/S插入數據和1000次/S讀取數據,因此GC會比較頻繁)
能夠看出老生代內存獲得有效回收,內存泄露的問題獲得解決 服務器

相關文章
相關標籤/搜索