前言
最近剛上線了一款社交專案,運行十多天后(運營持續每天推量),發現問題:
- 系統OOM(資源不能被釋放)導致服務器頻繁且長時間FGC導致服務器CPU持續飚高
- 日志中記憶體溢位:java.lang.OutOfMemoryError: Java heap space
- 程式十分卡頓,嚴重影響用戶使用
從以下方面,為大家分享此次問題解決流程
- 問題出現現象
- 臨時解決方案
- 復現問題
- 定位問題發生原因
- 優化代碼
- 優化后進行壓測,上線
- 復盤
學完本博文,你的識訓
- 排查記憶體溢位的思路
- 排查記憶體溢位程序中用到的命令及工具(Linux命令,Eclipse Memory Anaylzer[MAT])
- 定位系統記憶體溢位的代碼,并進行優化
- 此次記憶體溢位問題復盤
解決方案流程圖

問題&臨時解決方案&定位問題&最終解決方案
-
問題:
-
業務反饋程式用的十分卡,同時測驗自己測的也十分卡
-
從ELK收集的請求日志發現確實存在問題,線上是兩臺部署:兩臺機器上都是,一次請求耗時由原來的幾毫秒變為10幾秒
-
CPU跑的過高,當時是4核,CPU持續飆到350%+;
-
當時一臺服務器CPU截圖:

-
-
臨時解決方案
- 當時為了減少對業務影響,直接將生產兩臺服務器上的專案進行重啟
- 專案啟動引數中沒有加記憶體溢位日志輸出(后續博客為大家介紹JVM調優時講解啟動命令中加記憶體溢位日志輸出),重啟后出問題時專案的JVM資訊丟失了
-
復現問題方式:在開發環境對程式進行持續壓測;壓測相關服務器配置:
-
服務器配置:8核,16G
-
專案啟動記憶體:136M
-
Jmeter持續(回圈)壓發訊息介面10分鐘
-
-
定位問題
-
top命令查看最耗CPU的行程(行程:17038;CPU持續飆到595%+)
# 輸入top命令后鍵入P(大寫P),行程按照CPU從高到底排序 top
-
查看該行程中最耗CPU的執行緒(發現有一些執行緒占用CPU較高)
# 17038為行程號,鍵入P(大寫P),該行程中的執行緒按照CPU從高到底排序 top -Hp 17038
-
將執行緒號轉為16進制,同時查看這些執行緒當前正在干什么(在此以17045執行緒為例)
# 將執行緒號轉為16進制;其中17045為執行緒號 printf '%x\n' 17045 # 17038為行程號,0x4295為最耗CPU執行緒的十六進制 jstack 17038 | grep '0x4295' -C10 --color
-
可以看到最耗CPU的執行緒都是在進行GC
-
用Jmap命令查看當前堆的使用情況(發現老年代現在已占用99.8%+)
# 其中17038為行程號 jmap -heap 17038
-
查看gc頻率的命令(其中O代表老年代占用率,FGC是FullGC次數,FGCT是fullGC時間;可以看出在頻繁FullGC但是老年代有資源一直釋放不掉)
# 其中17038為行程號,5000是指每5秒(5000毫秒)輸出一次 jstat -gcutil 17038 5000
-
通過分析出問題時線上日志發現記憶體溢位;至此定位到問題根源是記憶體溢位導致(有未釋放資源堆積,導致老年代被占滿,然后頻繁的FullGC但是資源一直釋放不了)
grep -m 10 'OutOfMemoryError' *.log
-
-
分析問題產生原因
-
由于線上當時直接重啟,未能保留當時的JVM記憶體檔案;在開發環境進行回圈壓測,復現線上問題,然后匯出dump檔案進行分析找到原因
-
生成dump檔案命令
# 其中fileName是匯出后dump名稱,pid為行程號 jmap -dump:format=b,file=fileName.dump pid -
將dump檔案匯出到本地,用Eclipse Memary Analysis(MAT官網下載地址) 進行分析
-
MAT匯入dump檔案


-
按物件排序視圖進行查看(總覽中看到物件總個數:14.1百萬個)

-
發現有兩個類(ClassClassPath,ClassClassPathList)占用比較大,這兩個類約占物件總數的83%(計算方式:5873361*2/14100000=83%)

-
-
分析代碼
-
去代碼中全域搜這兩個類,發現只有在打日志的時候用到ClassClassPath類

-
分析ClassClassPath相關代碼:
-
用到ClassClassPath物件是一個靜態的ClassPool;
-
問題原因:classPath一直被靜態的全域pool所持有,導致GC一直釋放不掉;




-
當然順著代碼,順藤摸瓜也找到了ClassPathList


-
-
-
優化代碼:每次用完ClassClassPath后將其釋放
- 每次物件使用完后從靜態pool中移除
- 注意:classPath=null這種方式是不能釋放掉的

-
優化后再次進行驗證
- 開發環境回圈壓測,用MAT分析dump檔案,發現記憶體中已不再堆積ClassClassPath類;優化前后介面吞吐量也提升8.2%
- 進行線上發布,觀察一周后,對記憶體分析發現正常
-
復盤:
- 專案比對:
- 為快速開發,社交的代碼從原來金融專案基礎上改造而來;
- 原來金融專案沒有記憶體溢位,而社交專案為什么記憶體溢位?
- 通過ELK統計一段時間的訪問量結果:
- 社交目前日訪問后臺量65w+
- 金融專案只有4.5W+
- 社交和金融專案業務型別不一樣,所呈現出的特點也不同
- 去生產的金融專案中dump記憶體檔案,用MAT工具分析,發現也存在ClassClassPath類堆積釋放不掉,只不過由于訪問量少,堆積量未占滿老年代而已;果斷在金融專案迭代時將其優化;
- 專案比對:
-
程式預警:為減少業務影響,增加介面耗時的預警(后續博文為大家共享);實作方式:
- 在每次程式處理完進行預警(比如本次請求>閾值);缺點:消耗性能影響正常業務
- 在ELK清洗時用相關插件進行預警;優點:和業務解耦,對業務無影響 -
服務器預警:運維增加CPU記憶體,日志記憶體溢位監控
總結
-
解決記憶體溢位程序總結:
-
不同的專案導致記憶體溢位原因是不同的;
-
重要的是排查思路
-
經過不斷的耐心的去觀察,測驗,分析才能定位到問題并最終解決問題
-
-
在這次分析記憶體溢位程序中,我們也針對我們專案的JVM啟動引數進行了調優,在接下來的博文中為大家分享JVM調優
CSDN認證博客專家
分布式
Spring
Redis
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/192590.html
標籤:其他
下一篇:H3C(28)——IPSec
