問題
收到服務內存佔用過大告警,登錄虛擬機使用top
發現每隔幾秒java進程佔用的CPU就會暴增一次。
排查方向一:服務日誌
- 使用
tail -fn 100 xxx.log
查看服務日誌,發現頻繁打印連接mail服務器失敗錯誤,根據錯誤堆棧信息定位到業務代碼位置 - 定位業務代碼中的錯誤
排查方向二:JVM工具
若代碼中未打印出錯信息,可以考慮從jvm線程上入手。
- 使用
jps -mlv
獲取java服務pid - 使用
top -H -p pid
查看高佔用線程,CPU打滿時有10多個線程都佔10%左右 - 使用
jstat -gc pid 1000
及jstat -gcutil pid 1000
查看gc情況,發現大約每隔1到2秒jvm進行一次minor gc,老年代空間幾乎不變,懷疑業務邏輯導致不斷創建、釋放新對象 - 記錄步驟2中高佔用率的幾個線程tid,使用
printf %x\n tid
將tid轉爲16進制 - 使用
jstack pid
打印線程狀態,觀察未出現死鎖,搜索上一步的16進制數對應的線程,發現繁忙線程果真大都是gc線程,同時參雜着自定義線程池的線程,找到running狀態的線程,根據線程調用棧定位到代碼中執行的位置 - 業務代碼中線程正在執行的是郵件發送操作,與方向一種錯誤位置吻合
- 定位到錯誤根源:該機器未開啓外網訪問權限,而代碼業務邏輯中檢測郵件發送連接失敗則直接扔回隊列中重試,因爲沒有設定最大重試次數導致頻繁創建郵件消息體,耗盡堆空間,打開外網後問題解決
輔助分析
使用jmap -dump:format=b,file=dump.bin pid
生成堆轉儲文件,使用jhat dump.bin
或複製到有VisualVM等分析工具的機器上進行分析,可以發現佔用堆空間最大的是char[]數組,打開其中幾個發現內容都是一致的,都是郵件發送的消息體。
擴展
- 可以結合
free
、vmstat
、iostat
、netstat
等系統命令進行分析排查 - 將上述步驟寫成腳本方便快速定位問題,網上已有不少例子,如 https://github.com/oldratlee/useful-scripts