【Java內存溢出排查】gc監測以及內存突增問題排查

前情提要

文檔:【Java內存溢出排查】測試環境服務器掛...

鏈接:http://note.youdao.com/noteshare?id=783e7ec89950f4167867ef3ef33470b6&sub=48AEFC6FDECB4C60869FAA5FABF57AB0

 

通過以下命令信息可以確定是內存溢出,且Full GC後內存無法得到回收

top -Hp 1 (發現JVM Thread佔用 CPU200%)

jstat -gccause 1 3s (發現大量full gc,且full gc後內存沒有得到回收)

jmap -heap 1 (發現新生代、老年代used佔比均99.99%)

 

 

問題分析

 

新版本代碼部署到預發佈環境後,同樣出現了頻繁Full GC(1w+次),CPU飆高而堆內存回收不了的問題,在代碼發佈上線之前,必須排查出到底原因是什麼?

之前針對測試環境的現場信息分析,基本可以確定不存在比較明顯的內存泄漏(即使存在,也不會是導致服務掛掉的根本原因)

 

這裏再次用新的方法說明爲什麼得出這個結論——

 

 

線索一:MAT(Memery Analysis Tools)指出的唯一內存泄漏可能

根據MAT對我們測試環境怪掉時的堆內存快照分析,給出的唯一的內存泄漏建議如下,換句話說,有一定量ParallelWebappClassLoader對象的引用出現了問題,導致GC無法正常回收這部分內存,那麼一定量指的是多少?12.39%,也就是存在內存泄漏問題的話,對整個堆內存溢出問題的影響是非常小的。一般比較明顯的內存溢出問題,佔比會達到70%以上。

ParallelWebappClassLoader到底是不是內存泄漏?

如果不是ParallelWebappClassLoader對象引用造成的,服務器內存溢出的根因到底又是什麼?

 

 

線索二:GC後的內存佔用情況

 

下面是測試環境服務器正常情況下,執行jmap -histo:live pid | head -n 23 命令的截圖,可以得到當前內存中排名前20的對象實例和class等信息。

 

這裏說明下jmap -histo:live [pid]命令,執行後,將會觸發一次Full GC,得到的執行結果是Full GC後的內存對象情況。

通過jstat -gccause [pid]命令也可以看到jmap執行後的GC原因,會顯示 “Heap Inspection Initiated GC”

 

從instances列來看,[C、[I、[B排名靠前是正常的,畢竟是基礎數據類型(char\int\byte)。

當時最先懷疑的是綠色框中的ConcurrentHashMap$Node對象,這可能是某些比較大的map造成的,一般內存泄漏也會是各種map或list持有引用導致的。

 

所以直接執行下面命令重點觀察ConcurrentHashMap$Node對象的實例數(instances)

jmap -histo:live 1 | grep java.util.concurrent.ConcurrentHashMap$Node

 

得到如下結果,簡單總結一下結果,下面的執行信息太長可以跳過……

1)隨着服務被持續調用,java.util.concurrent.ConcurrentHashMap$Node對象實例數會不斷累加,但GC過後也得到了回收

2)一定時間段內,ConcurrentHashMap$Node對象的回收慢於其增長數量,回收效率比較低

num     #instances         #bytes  class name
----------------------------------------------
   1:        516515       77064424  [C
   2:         30478       39696288  [B
   3:        337998       29743824  java.lang.reflect.Method
   4:         16370       18795648  [I
   5:        510907       12261768  java.lang.String
   6:        374742       11991744  java.util.concurrent.ConcurrentHashMap$Node
   7:        222880       10698240  org.aspectj.weaver.reflect.ShadowMatchImpl
   8:        130860        7360464  [Ljava.lang.Object;
   9:        222880        7132160  org.aspectj.weaver.patterns.ExposedState
root@team-app-service-2-599746d64b-h6tzj:/usr/local/tomcat# jmap -histo:live 1 | grep java.util.concurrent.ConcurrentHashMap$Node
   6:        374823       11994336  java.util.concurrent.ConcurrentHashMap$Node
  18:          5441        2834704  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  52:          7680         491520  java.util.concurrent.ConcurrentHashMap
 561:           107           5136  java.util.concurrent.ConcurrentHashMap$TreeNode
 744:           149           2384  java.util.concurrent.ConcurrentHashMap$EntrySetView
 927:             4           1120  java.util.concurrent.ConcurrentHashMap$CounterCell
1029:            34            816  java.util.concurrent.ConcurrentHashMap$KeySetView
1190:            11            528  java.util.concurrent.ConcurrentHashMap$TreeBin
1719:            12            192  java.util.concurrent.ConcurrentHashMap$ValuesView
3436:             2             48  [Ljava.util.concurrent.ConcurrentHashMap$CounterCell;
root@team-app-service-2-599746d64b-h6tzj:/usr/local/tomcat# jmap -histo:live 1 | grep java.util.concurrent.ConcurrentHashMap$Node
   6:        374851       11995232  java.util.concurrent.ConcurrentHashMap$Node
  18:          5455        2835824  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  52:          7694         492416  java.util.concurrent.ConcurrentHashMap
 564:           107           5136  java.util.concurrent.ConcurrentHashMap$TreeNode
 747:           149           2384  java.util.concurrent.ConcurrentHashMap$EntrySetView
 930:             4           1120  java.util.concurrent.ConcurrentHashMap$CounterCell
1033:            34            816  java.util.concurrent.ConcurrentHashMap$KeySetView
1197:            11            528  java.util.concurrent.ConcurrentHashMap$TreeBin
1719:            12            192  java.util.concurrent.ConcurrentHashMap$ValuesView
3435:             2             48  [Ljava.util.concurrent.ConcurrentHashMap$CounterCell;
root@team-app-service-2-599746d64b-h6tzj:/usr/local/tomcat# jmap -histo:live 1 | grep java.util.concurrent.ConcurrentHashMap$Node
   6:        375425       12013600  java.util.concurrent.ConcurrentHashMap$Node
  18:          5742        2858784  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  51:          7981         510784  java.util.concurrent.ConcurrentHashMap
 581:           107           5136  java.util.concurrent.ConcurrentHashMap$TreeNode
 760:           149           2384  java.util.concurrent.ConcurrentHashMap$EntrySetView
 939:             4           1120  java.util.concurrent.ConcurrentHashMap$CounterCell
1039:            34            816  java.util.concurrent.ConcurrentHashMap$KeySetView
1197:            11            528  java.util.concurrent.ConcurrentHashMap$TreeBin
1719:            12            192  java.util.concurrent.ConcurrentHashMap$ValuesView
3435:             2             48  [Ljava.util.concurrent.ConcurrentHashMap$CounterCell;
root@team-app-service-2-599746d64b-h6tzj:/usr/local/tomcat# jmap -histo:live 1 | grep java.util.concurrent.ConcurrentHashMap$Node
   6:        374815       11994080  java.util.concurrent.ConcurrentHashMap$Node
  18:          5437        2834384  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  52:          7676         491264  java.util.concurrent.ConcurrentHashMap
 561:           107           5136  java.util.concurrent.ConcurrentHashMap$TreeNode
 744:           149           2384  java.util.concurrent.ConcurrentHashMap$EntrySetView
 926:             4           1120  java.util.concurrent.ConcurrentHashMap$CounterCell
1028:            34            816  java.util.concurrent.ConcurrentHashMap$KeySetView
1190:            11            528  java.util.concurrent.ConcurrentHashMap$TreeBin
1719:            12            192  java.util.concurrent.ConcurrentHashMap$ValuesView
3435:             2             48  [Ljava.util.concurrent.ConcurrentHashMap$CounterCell;
root@team-app-service-2-599746d64b-h6tzj:/usr/local/tomcat# jmap -histo:live 1 | grep java.util.concurrent.ConcurrentHashMap$Node
   6:        374850       11995200  java.util.concurrent.ConcurrentHashMap$Node
  18:          5450        2835488  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  51:          7690         492160  java.util.concurrent.ConcurrentHashMap
 563:           107           5136  java.util.concurrent.ConcurrentHashMap$TreeNode
 745:           150           2400  java.util.concurrent.ConcurrentHashMap$EntrySetView
 928:             4           1120  java.util.concurrent.ConcurrentHashMap$CounterCell
1030:            34            816  java.util.concurrent.ConcurrentHashMap$KeySetView
1192:            11            528  java.util.concurrent.ConcurrentHashMap$TreeBin
1718:            12            192  java.util.concurrent.ConcurrentHashMap$ValuesView
3431:             2             48  [Ljava.util.concurrent.ConcurrentHashMap$CounterCell;
root@team-app-service-2-599746d64b-h6tzj:/usr/local/tomcat# jmap -histo:live 1 | grep java.util.concurrent.ConcurrentHashMap$Node
   6:        375404       12012928  java.util.concurrent.ConcurrentHashMap$Node
  18:          5697        2855376  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  51:          7940         508160  java.util.concurrent.ConcurrentHashMap
 576:           107           5136  java.util.concurrent.ConcurrentHashMap$TreeNode
 762:           152           2432  java.util.concurrent.ConcurrentHashMap$EntrySetView
 943:             4           1120  java.util.concurrent.ConcurrentHashMap$CounterCell
1042:            34            816  java.util.concurrent.ConcurrentHashMap$KeySetView
1202:            11            528  java.util.concurrent.ConcurrentHashMap$TreeBin
1732:            12            192  java.util.concurrent.ConcurrentHashMap$ValuesView
3456:             2             48  [Ljava.util.concurrent.ConcurrentHashMap$CounterCell;
root@team-app-service-2-599746d64b-h6tzj:/usr/local/tomcat# jmap -histo:live 1 | grep java.util.concurrent.ConcurrentHashMap$Node
   6:        374882       11996224  java.util.concurrent.ConcurrentHashMap$Node
  18:          5449        2835472  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  51:          7690         492160  java.util.concurrent.ConcurrentHashMap
 559:           107           5136  java.util.concurrent.ConcurrentHashMap$TreeNode
 746:           151           2416  java.util.concurrent.ConcurrentHashMap$EntrySetView
 928:             4           1120  java.util.concurrent.ConcurrentHashMap$CounterCell
1029:            34            816  java.util.concurrent.ConcurrentHashMap$KeySetView
1191:            11            528  java.util.concurrent.ConcurrentHashMap$TreeBin
1718:            12            192  java.util.concurrent.ConcurrentHashMap$ValuesView
3431:             2             48  [Ljava.util.concurrent.ConcurrentHashMap$CounterCell;
root@team-app-service-2-599746d64b-h6tzj:/usr/local/tomcat# jmap -histo:live 1 | grep java.util.concurrent.ConcurrentHashMap$Node
   6:        375174       12005568  java.util.concurrent.ConcurrentHashMap$Node
  18:          5462        2838848  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  51:          7701         492864  java.util.concurrent.ConcurrentHashMap
 560:           107           5136  java.util.concurrent.ConcurrentHashMap$TreeNode
 741:           153           2448  java.util.concurrent.ConcurrentHashMap$EntrySetView
 926:             4           1120  java.util.concurrent.ConcurrentHashMap$CounterCell
1024:            34            816  java.util.concurrent.ConcurrentHashMap$KeySetView
1190:            11            528  java.util.concurrent.ConcurrentHashMap$TreeBin
1714:            12            192  java.util.concurrent.ConcurrentHashMap$ValuesView
3431:             2             48  [Ljava.util.concurrent.ConcurrentHashMap$CounterCell;
root@team-app-service-2-599746d64b-h6tzj:/usr/local/tomcat# jmap -histo:live 1 | grep java.util.concurrent.ConcurrentHashMap$Node
   6:        376732       12055424  java.util.concurrent.ConcurrentHashMap$Node
  18:          5818        2866688  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  51:          8078         516992  java.util.concurrent.ConcurrentHashMap
 574:           107           5136  java.util.concurrent.ConcurrentHashMap$TreeNode
 757:           154           2464  java.util.concurrent.ConcurrentHashMap$EntrySetView
 949:             4           1120  java.util.concurrent.ConcurrentHashMap$CounterCell
1051:            34            816  java.util.concurrent.ConcurrentHashMap$KeySetView
1230:            11            528  java.util.concurrent.ConcurrentHashMap$TreeBin
1788:            12            192  java.util.concurrent.ConcurrentHashMap$ValuesView
3509:             2             48  [Ljava.util.concurrent.ConcurrentHashMap$CounterCell;

 

所以分析ConcurrentHashMap$Node對象可能存在泄漏,在通過MAT查看該對象的引用情況(排除弱引用),根據shallow heap字段倒序排序,得到結果證實了MAT指出的內存泄漏問題對象ParallelWebappClassLoader,說明MAT分析是有道理的,所以可以確定這裏存在比較“輕微”的內存泄漏問題。

 

爲什麼說是“輕微”的內存泄漏問題?接着就要分析下,服務掛掉的時候,這個ConcurrentHashMap$Node對象佔用堆內存12307392b≈11.73M,而當時老年代大小爲337M,所以不是它壓垮的服務器。

這裏也可以直接看下ParallelWebappClassLoader對象的Retained Heap(深堆內存)大小,這個值意味着如果該對象被回收,將獲得多少的內存收益。

 

線索三:內存突增

 

當時只關注了內存泄漏這個點,忘記了流量突增或者是某些特定代碼查詢的數據量突增,也可能導致內存溢出,特別是在老年代設置得比較小的情況下。

而我們測試環境的流量不可能產生突增,基本就是測試同學在進行功能測試,QPS也比較低。從sky walking監控服務掛掉時的請求量也都還算正常

但看內存監控發現有內存的突增,14:53到14:54一分鐘從402M突增到了462M,並且之後還在突增到483M(這時Full GC後老年代used佔比依然是99.99%),測試環境堆的總大小500M,所以服務器內存溢出,應該是這個時間段的某段代碼執行導致的

 

問題排查

 

思路一:排查接口

方向找到了,首先想到是分析關鍵時間段內的接口請求,能夠一下子填滿60+M的數據請求,接口耗時應該會挺長,因爲在內存中遍歷數據需要時間,再加上服務IO也需要時間。

不過由於測試環境日誌被清理了,預發佈環境有沒有監控信息,從接口爲入口的線索斷掉了(況且接口響應慢也有可能是因爲線程阻塞等其他資源競爭,沒有說服力)

 

思路二:根據內存快照,對比突增內存對應的對象及其引用

沒有接口日誌也沒關係,我對比了一下測試環境,服務器內存溢出時(下圖1)的快照和服務器正常工作時(觀察了比較久,數據穩定)的內存快照(下圖2)

內存增加了47M(圖3),還沒算上其他的一些incoming引用對象的大小,那麼這個突增的大小是對應得上前面分析得問題根因的。

(圖1)

 

(圖2)

(圖3)

 

所以可以在服務掛掉時,且執行過GC後的堆內存快照中排查分析“[C”和“[B”這兩塊內存數據,可以更準確的找到這部分出問題的數據,最終找到問題代碼。

先展開char[]對象和其引用,看看具體是什麼東東導致比正常服務時多出來30M數據。

按照Shallow Heap(淺堆空間,即對象實際佔用內存大小)倒敘排序,找一些問題對象看看(哪些是問題對象?1體積比較大的;2體積不大,但個數驚人的)

 

對比時突然發現當時測試環境怪掉時拿到的堆快照和服務正常時的快照數據幾乎一致,暫時還不知道是不是文件下載錯了,所以目前只能調整測試環境JVM配置,等測試環境再次出現問題時再dump一次堆快照,然後建立可用對照組進一步分析。


 

再次把服務器搞掛一次之後,拿到監控數據,頻繁Full GC,各堆內存區域used 99%

此時的堆內存對象佔用情況如下,基本和上一次掛掉的數據一致。

MAT建立對照組,分析問題數據,發現問題對象[C 的引用有一個ElasticSearchGroupUnitField,和客源匹配小組日記查詢有關,是ES搜索服務提供的接口。

一個大char[]對象的實際內存佔用了17141776b ≈ 16.34M,難以想象這個接口發佈到線上,多線程請求執行時服務器能撐多久……

這就是導致測試環境服務器掛掉的根本原因!

 

 

問題代碼分析

 

查詢某個客戶匹配的小組日記列表時,沒有加入小組id條件,而設置的pageSize爲1000條,在沒有指定groupId的情況下,整個小組日記中搜索與之匹配的日記數據,那肯定是每次都是滿載的1000條,而日記的字段又比較多,單個文檔的數據量比較大。

這個業務方法在客源列表、客源詳情、客源匹配日記接口等都有調用,所以將代碼發佈到測試環境後,針對性測試了幾分鐘服務就掛了,問題復現。

 

修復邏輯比較簡單,就是加入需要查詢的小組id作爲查詢條件,修復後,再次針對以上場景進行測試,GC回收正常,問題解決。

 

總結

 

1. 排查服務器內存性能問題應該從2個大方向獲取信息進行分析:

          a. 內存泄漏

          b. 內存(流量)突增

   先確定是什麼病,再對症下藥,以免繞彎子…

 

2. 加強code review的執行力度,畢竟code review的成本比這樣一頓排查和分析要小得多,反向的排查總是要複雜和困難的

 

最後,還是感謝 trouble maker,還有幫忙驗證的測試同學!

 

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