關於Java Tomcat 內存溢出排查心得分享

我網站不知道什麼時候,開始內存飆升,從   Tomcat  啓動後,初始內存佔用4%~5% 左右,到20%、40% 最後服務器卡死,SSH都連不上服務器,不得不重啓。但是我知道是我程序的問題。然後分析問題,解決問題。陸陸續續持續了一個多月,下面分享解決思路。

一、定位造成內存溢出可能存在的問題

  1. io流操作文檔沒關閉流。
  2. 往一個靜態集合變量裏一直壓棧。
  3. 連接沒釋放。
  4. Java隊列沒消耗。
  5. Ehcache緩存使用量過大。
  6. 頻繁IO操作大文件。
  7. Session過期時間太久。
  8. 等等.....

我定位有可能造成的原因是以上原因,針對本站的特點在做細排查,有可能出現的問題。

  1. io流操作文檔沒關閉流。(有可能)
  2. 往一個靜態集合變量裏一直壓棧。(沒有這個問題)
  3. 鏈接沒釋放。(有可能,因爲本站有大量的HttpClient請求)
  4. Java隊列沒消耗。(有可能,因爲本站使用上了)
  5. Ehcache緩存使用量過大。(沒使用)
  6. 頻繁IO操作大文件。(沒有)
  7. Session過期時間太久。(可能有)
  8. 等等.....

二、採用Memory Analyzer Tool(MAT)分析Java內存

採用   jmap  命令(Java Memory Map)導出內存轉儲快照(Dump);

首先查詢到你對應的   Tomcat  的pid

  1. ps -aux|grep xxx-tomcat

然後執行jmap命令:

  1. jmap -dump:format=b,file=73630.hprof 16706

導出完畢。down下來用   Eclipse  ,或者   MyEclipse  查看,但是 MyEclipse 或者 Eclipse 要先安裝工具,自行百度。然後以openFile的方式打開。如圖:

可能有點看不懂,自行解決,點擊Histogram ,可以看到內存中的詳細信息。

可以看到char[] byte[] 佔用的是最多,而且不是多一點點。這明顯不正常。就是一些IO流相關的信息。Memory Analyzer 工具還是有很多功能的,我也不太會用。具體可以多看看相關的博客。下面來排查問題。

三、問題逐一排查,由容易到複雜

3.1 Session檢查

從配置文件web.xml 查看,發現   Session    超時配置了900 分鐘。。。醉了,回想起來,是當時因爲有權限校驗(防止攻擊)模塊利用   Session  來實現,所以才出此下策。改成30 分鐘,重啓後效果有一點點。繼續排查。

3.2 IO流操作沒關閉檢查(嚴重)

全局搜索各種InputStream OutputStream ,各種Buffer 等等,然後各種修改關閉。尤其是本站的  HTTP  模擬請求工具,一天的用量非常大。如下IO 流在finally try...catch 各種關閉。

  1. try{
  2. //......
  3. }catch(Exception e){
  4. //......
  5. }finally{
  6. realUrl = null;
  7. try {
  8. if(null != conn)conn.disconnect();
  9. conn = null;
  10. } catch (Exception e2) {
  11. LoggerUtils.fmtError(HttpManager.class, e2, "請求完畢關閉流出現異常,可以忽略![%s]", url);
  12. }
  13. try {
  14. if(null != outStream)outStream.close();
  15. outStream = null;
  16. } catch (Exception e2) {
  17. LoggerUtils.fmtError(HttpManager.class, e2, "請求完畢關閉流出現異常,可以忽略![%s]", url);
  18. }
  19. try {
  20. if(null != out)out.close();
  21. out = null;
  22. } catch (Exception e2) {
  23. LoggerUtils.fmtError(HttpManager.class, e2, "請求完畢關閉流出現異常,可以忽略![%s]", url);
  24. }
  25. try {
  26. if(null != inStream)inStream.close();
  27. inStream = null;
  28. } catch (Exception e2) {
  29. LoggerUtils.fmtError(HttpManager.class, e2, "請求完畢關閉流出現異常,可以忽略![%s]", url);
  30. }
  31. try {
  32. if(null != in)in.close();
  33. in = null;
  34. } catch (Exception e2) {
  35. LoggerUtils.fmtError(HttpManager.class, e2, "請求完畢關閉流出現異常,可以忽略![%s]", url);
  36. }
  37. double end = System.currentTimeMillis();
  38. map.put("time", (end - begin) / 1000);
  39. //大對象用完賦值null
  40. bo = null;//促進回收
  41. }

結論:全部加好後重啓,過一段時間再看。效果不明顯。其實是我在平時代碼嚴謹上這個錯誤沒有出現,但是從經驗角度來說,如果這個沒處理好,這個是最容易出現 內存溢出  的。

ps:關於後面有一段代碼,bo=null;//促進回收 ,我個人是這麼理解,不知道有沒毛病。主要是針對局部變量的大變量。可以用完後賦值爲null 

3.3 HttpClient請求鏈接釋放問題(嚴重)

  Httpclent  請求鏈接不主動關閉,這個問題也是個大問題,但是對內存的影響,看從什麼角度,佔用最大的應該還是響應鏈接,把鏈接用完了,新的鏈接就進不來,我們知道  Tomcat  默認配置一共好像才150 個,一會就用完了,如果不用完關閉,那麼會造成鏈接釋放慢,甚至不釋放。如果不釋放,請求得到的responseBody 那麼有可能一直沒有釋放了。

HttpClient怎麼釋放?

其實百度一下有很多答案,我這裏順便帶一下。

1.請求頭增加關閉Head信息。

  1. //添加頭信息
  2. HttpURLConnection conn = null;
  3. URL realUrl = new URL(url);
  4. // 打開和URL之間的連接
  5. conn = (HttpURLConnection) realUrl.openConnection();
  6. //省略部分代碼
  7. //增加請求完畢後關閉鏈接的頭信息
  8. conn.setRequestProperty("Connection", "close");

2.用完Httpclent後手動關閉

  1. //添加頭信息
  2. HttpURLConnection conn = null;
  3. URL realUrl = new URL(url);
  4. // 打開和URL之間的連接
  5. conn = (HttpURLConnection) realUrl.openConnection();
  6. //省略部分代碼
  7. //手動釋放
  8. if(null != conn)conn.disconnect();

3.通過線程的方式掃描關閉。

這個方法其實類似啓動一個守護線程(一直啓動着),來掃描有沒有關閉的請求。這個方法比較雞肋,用的好就很好,用的不好就蛋痛了。推薦使用方法一、方法二,爲了保險起見你可以兩種一起使用,並不會有問題。

總結:  Httpclent  鏈接請求完畢一定要關閉。有的人可能會看了瀏覽器裏的請求頭信息爲:Connection:keep-alive ,這個回頭我會詳細說明,但是這個是瀏覽器需要的,因爲你還要繼續加載css、js、image 等等,大概是這個意思,而你的  Httpclent  只需要加載一次,所以直接close 即可。

3.3 Java隊列(最終問題定位)

昨晚把  隊列  換成了阿里的隊列,問題解決了,幾個小時過去了,還是5.6% 

換成了阿里的  隊列  ,我把隊列用於本地計算機跑,線上跑網站,把所有隊列的錯誤信息,以及執行情況看了一下,發現是之前邏輯寫的有問題,導致隊列異常,隊列異常沒有catch及時處理。故導致了這個現象的最大的罪魁禍首。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章