我不是bug神(JVM問題排查)


  Story background

  回望2018年12月,這也許是程序員們日夜不得安寧的日子,皆因各種前線的系統使用者都需要沖業績等原因,往往在這個時候會向系統同時寫入海量的數據,當我們的應用或者數據庫服務器反應不過來的時候,就會產生各種各樣詭異的問題,諸如表現出來就是系統變得巨卡無比,無法使用,或者周期性卡頓,令人發指,用戶輕則問候系統全家,重則心臟病發。總而言之每天都腦殼疼!歸根到底是我們的應用服務器數據庫服務器因為扛不住流量造成的系統BUG問題暴露,諸如OOM等,呈現出機器的三高,這里說的三高並不是高血脂、高血壓、高血糖。而是高CPU,高內存,高NETWORK/IO!(本文只講述應用服務器,暫不講述數據庫服務器造成的問題)PS:為什么不做壓測?石麗不允許呀

  Begin Transaction

  而小魯班恰好是參與這個問題系統的開發者之一,這天小魯班起得比較晚,到了公司都快10點了,不過沒所謂,最近都沒什么新需求要做,小魯班打開電腦正准備一天的摸魚旅途。忽然之間門口傳來一陣急促的腳步聲,小魯班回眸一看,噢原來是項目經理提着大刀殺來了,詢問之原來前線系統又掛了,這些日子里看到系統天天被投訴,其實也責無旁貸,小魯班心里想系統掛了不是運維的事情么,別魯班抓老鼠了!不過心里又想TM技術運維好像刪庫跑路了。沒辦法,小魯班臨危受命硬着頭皮上吧。

  回顧一下問題系統的架構,是典型的應用集群服務器。理論上來說,有做應用集群的話,只要有一個應用服務器沒死,一個數據庫服務器沒死,系統還是可以正常運行的,只是處理請求的平均速度有所降低。不管怎么說,只要有一台應用或數據庫掛了,基本上都是因為代碼存在bug,導致三高,使得機器無法發揮其本來的性能,如果未能及時發現處理,其他集群的服務器機器也很快被同一個BUG拖垮,例如死鎖,最終導致沒有一台機器能提供服務,集群系統最終走向癱瘓,換句通俗的話來說就是系統bug的存在使得機器生活不能自理,所以更不能向外提供服務。

  Try

  小魯班盡管是科班出生的,但對於此類系統線上問題,一時之間還真摸不着頭腦,無奈之下只好請教小魯班的表哥魯班大師,要知道,魯班大師可是有着一套教科書式的解決方案,並且可以通過一些途徑來定位並解決系統bug,特別是代碼原因產生的問題,從根本上杜絕問題,而不是每次出現問題都重啟應用服務器就算了,因為知道bug一天不除,系統永遠都可能會隨時隨地go die。

  於是小魯班立馬拿起大哥大手機向表哥發了個紅包,沒想到這TM就把他表哥魯班大師炸了出來,並取得了

  解決方案三部曲

  1.查看應用服務器指標(CPU,內存,NETWORK,IO),並保留現場所有線索日志,然后進行問題歸類和分析,最后重啟或回滾應用,以達到先恢復服務,后解決BUG的目的

  2.通過分析排查工具或命令對1步驟中取得的線索日志進行推測分析,或許能定位到bug代碼部分,或許能發現JVM某些參數不合理

  3.修改bug代碼或調節JVM參數,達到修復bug的目的,晚上重新發布應用

 

  不妨畫一張圖

   

 

  那么具體是怎么操作的呢

 1.首先當然是查看機器的指標啦,Linux則是使用TOP和JPS命令列出進程以及各個進程所占用的CPU和內存使用情況。而windows則是直接打開任務管理器就可以看到,但無論什么操作系統的服務器,我們都可以通過JConsole這個JDK自帶的可視化工具遠程連接來看到內存的使用趨勢,這樣一下來,我們就能大致確定是否因為內存出現問題了,例如下圖出現的CPU滿的問題,但是我們無法知道是什么原因導致的問題。

 

 2.要知道是什么原因導致的,我們得要知道目前JVM是用什么垃圾回收器,畢竟有些回收器是CPU敏感有些則不是,通過命令-XX:+PrintFlagsFinal 打印JVM所有參數

或者-XX:+PrintCommandLineFlags通過打印被修改過的參數都可以知道,這個可以幫助我們分析是發生內存問題的原因。

  

 3.然后我們得打印GC日志,通過日志分析,其實可以發現很多線索,比如通過GC發生的次數,來判斷堆的大小是否是合理的范圍,通過發生GC后,回收的對象容量多少來判斷是否存在內存泄漏嫌疑,特別是FGC,系統卡頓是否與STW有關等等

  • -XX:+PrintGCTimeStamps
  • -XX:+PrintGCDetails
  • -Xloggc:/home/administrator/zqs/gc.log

  那么日志怎么看呢,如圖吧

  

 

    4.假如發生了內存問題,這個時候需要通過打印內存使用量來定位問題點Jstar  

  實時查看gc的狀況,通過jstat -gcutil 可以查看new區、old區等堆空間的內存使用詳情,以及gc發生的次數和時間

  執行:cd $JAVA_HOME/bin中執行jstat,注意jstat后一定要跟參數。具體命令行:jstat -gcutil 'java進程PID' 1000

   

  如圖所示,發現Eden區、Old區都滿了,此時新創建的對象無法存放,晉升到Old區的對象也無法存放,所以系統持續出現Full GC了。

 

  5.如果看不到哪里出問題了,需要把內存鏡像導出,然后用專業的工具去分享鏡像如MAT

  當然了,我們也可以設置當發生OOM的時候,自動導出進程鏡像

  • -XX:+HeapDumpOnOutOfMemoryError
  • -XX:HeapDumpPath=/home/administrator/zqs/oom.hprof

   把DUMP文件導入MAT會非常直觀的展現內存泄漏的可疑點,可以直接看到某個線程占用了大量的內存,找到某個對象,常見的都是集合對象被GC ROOT指向等一直不能被回收

  

  

  

  catch

  當然以上是內存出現的問題,但是如果是CUP高出現的問題,會比較好搞一些,因為線程是運行在CPU上的,而線程卻可以通過分析棧信息獲取,而我們寫的方法不也是運行到棧上的嗎

  可使用top -H查看線程的cpu消耗狀況,這里有可能會看到有個別線程是cpu消耗的主體,這種情況通常會比較好解決,可根據top看到的線程id進行十六進制的轉換,用轉換出來的值和jstack出來的java線程堆棧的nid=0x[十六進制的線程id]進行關聯,即可看到此線程到底在做什么動作,這個時候需要進一步的去排查到底是什么原因造成的,例如有可能是正則計算,有可能是很深的遞歸或循環,也有可能是錯誤的在並發場景使用HashMap等。

  1.找到最耗CPU資源的java線程TID執行命令:ps H -eo user,pid,ppid,tid,time,%cpu,cmd | grep java | sort -k6 -nr |head -1

  2.將找到的線程TID進行十六進制的轉換

  3.通過命令導出堆棧文件:jstack javapid > jstack.log,根據上一步得到的線程TID,搜索堆棧文件,即可看到此線程到底在做什么動作。如對像鎖互鎖等,業務邏輯存在計算的死循環等

  我們可以得到類似一張這樣的圖,百度找的,可以定位到某個方法是否存在邏輯漏洞

  

 

  當然還可以用可視化工具jvisualvm,直接監控進程和線程,並且可以自動分析出一些明顯的BUG

 

  End Transaction

  小結

  系統突然掛了,怎么排查問題,首先得確定是內存出了問題,那出現內存問題該怎樣排查,對於這個問題,經常會聽到一些不全面的做法,那就是只通過一種方式來查問題。例如只通過Dump文件來查,或者只通過 Jconsole來查等等其實回答都對,但都不全面。例如,醫生在診斷病情時,很多情況下不會只相據B超單或驗血單來下結論,他們往往會根據多張化驗結果來綜合判斷。診斷內存問題也應該這樣。下面給出了各種方法的擅長點和同限性。

  

  最后總結一波:

  1.通過JConose確認發生內存問題

  2.在寫有可能導致內存問題的代碼時候,記錄日志,並打印當前內存使用量,用這些信息和GC日志來綜合判斷

  3.通過Dump文件重現OOM的內存鏡像 | 如果是CPU問題,可以考慮分析線程DUMP

  4.找到問題代碼修改

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM