背景
突然間接到運維的報警,我們一個服務,記憶體找過了6GB的佔用。才6GB 也不是很大,因為在處理別的事情,服務dump一下暫時一放,然後半小時之後,接到了運維的Kafka堆積報警。然後切換著重啟了一下兩個節點,Kafka消費速率回覆正常,記憶體也從500M攀升到2GB後逐漸穩定。當天半夜,運維又報警,不過已經熟睡的我,並沒有第一時間響應,第二天觀察了一下服務,發現記憶體已經迴歸到了2GB左右。
記憶體飆升 與 Kafka堆積是否是一件事情
拉出表格來對比一下時間發現,這兩個事情,時間節點基本上是能對的上的,所以大概率是一件事情。
dump分析
這麼看來只要解了記憶體飆升的問題,Kafka堆積的問題應該也就會一併解決。那麼,我們來分析一下,為什麼會出現記憶體飆升吧。
首先看一下記憶體的整體使用情況
!dumpheap -stat
發現跟字串關係很大,下邊的Free應該是記憶體釋放了,但是GC還沒有及時回收的記憶體。
字串1GB + 未回收記憶體3GB + 其他 林林總總 約等於 6G,差不多。下面我們重點追查一下這個字串是怎麼回事。
!DumpHeap -mt 000007fef956aee0 -min 200
出現了大量的 500Byte左右大小的字串。這個大小,感覺像是業務字串,並且應該是不重複的。先隨便搞一條出來看看。
!do 000000008966ee78
這個內容有點莫名的眼熟,這不是我們傳送訊息給釘釘,然後再查詢一下,釘釘的訊息傳送結果的返回值嘛?大概已經知道,是哪段邏輯出的錯了,程式碼雖然不是我寫的,但是我大概知道有這麼一段邏輯。可是這段為什麼會讓記憶體暴增呢?查一下引用堆疊。
!GCRoot 000000008966ee78
發現上層的引用是一個List,我們列印一下這個List看看。
!DumpObj 0000000313b371a0
發現這個List好像是不小,12000+。粗略算算 12000 * 500 / GB 大概 5.xGB,但是現在記憶體只用了1GB多點,分析一下原始碼。
那個List 應該就是對應了這個tasks物件了。
後邊會把查詢的資料,序列化,然後放到這個物件上,在插入到資料庫中。
簡單總結一下原因,是因為一次性取出來的物件有點多(12000)條,然後挨個查詢他的訊息的傳送結果,然後將網路呼叫的查詢結果,序列化到物件上,然後再儲存到資料庫中。因為這些Model都被引用在一個List中,所以當12000個資料全都被處理完成之後才會釋放List的記憶體。這樣,每次處理,記憶體都跟坐雲霄飛車一樣。
問題定位到了,解決方式就很簡單了,隨便搞。
啊,又解決一個問題,我變強了。