因爲怕踩高壓線的緣故很多內容不敢發,這篇文章記錄的事件實際是在2020年10月發生的.
這是工作以來見到的第N+起sql未做限制導致的內存溢出,依稀還記得上一次看到這類問題時寫的文章: https://my.oschina.net/110NotFound/blog/3129213
晚上19點40,同事報了測試環境某服務掛了,當時第一想法是重啓,但是重啓之後又出現了類似的問題,應用啓動後所有的接口不通。
看了下應用日誌,沒有明顯的報錯日誌刷新,但是ssh到機器上明顯感到卡頓,top命令一看,cpu佔用非常高。
top -H -p pid
看了下進程對應的線程:
將線程id轉換爲16進制之後在jstack查了下,對應的全是gc線程佔用cpu80%以上。
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f0ca401e000 nid=0x539 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f0ca4020000 nid=0x53a runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007f0ca4021800 nid=0x53b runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007f0ca4023800 nid=0x53c runnable
看了下gc日誌,3秒一次fullgc,一次gc平均2秒,說明此時去看jstack已經意義不大,應該關注下堆棧情況,什麼對象多導致了頻繁的gc。
先簡單的執行了下jmap看個大概:
jmap -histo:live 1335
結果如下:
八九不離十就是這個對象的問題了:
xxx.xxx.xxx.xxx.db.ProductAdRecognitionHistory
因爲是測試環境,所以用jmap命令沒有影響,其次應用此時已經無法提供服務了(生產環境慎重,會stw整個應用)。
導出整個堆棧:
jmap -dump:format=b,file=xxxxxheapdump.hprof pid
下載好eclipse的mat圖形化工具: MemoryAnalyzer
因爲堆棧文件有點大,需要設置下mat工具的最大分配堆內存, 修改 MemoryAnalyzer.ini 文件的最大堆內存爲 -Xmx8000m
打開 xxxxxheapdump.hprof ,加載完成後查看內存泄漏報告, 發現裏面是這樣的:
xxx.xxx.xxx.xxx.db.impl.ProductAdRecognitionHistoryDaoImpl$$EnhancerBySpringCGLIB$$44b54bd5.findByTraceId(Ljava/lang/String;)Ljava/util/List; (Unknown Source)
at xxx.xxx.xxx.xxx.ProductAdRecognitionHistoryServiceImpl.updateAidByTraceId(Ljava/lang/String;Ljava/lang/Long;)V (ProductAdRecognitionHistoryServiceImpl.java:xx)
at xxx.xxx.xxx.xxx.ProductIdentifyServiceImpl.updateRecognitionHistoryxxx(Ljava/lang/String;Ljava/lang/Long;)Z (ProductIdentifyServiceImpl.java:xx)
at xxx.xxx.xxx.xxx.ProductController.advertiseReturnxxx(Ljava/util/List;)xxx/xxx/xx/xx/xx/ServiceResponse; (ProductController.java:xx)
疑似內存泄漏的地方都指向了
ProductAdRecognitionHistoryServiceImpl.updateAidByTraceId
且裏面的 ProductAdRecognitionHistory 對象一次執行就有 80w個。
看了下代碼,updateAidByTraceId方法是這樣的:
問題就浮現了,當 traceId爲空的時候,會把所有的爲空的數據全部查詢出來,數據量80w+,和我們的堆棧分析是吻合的。
總結
查詢語句需要做改進,邏輯上要對這個查詢字段進行判斷校驗,同時查詢的sql也要限制查詢結果的長度,這樣可以防止查詢出的數據過大,打爆內存。