前言
對於後端程序員,特別是 Java 程序員來說,排查線上問題是不可避免的。各類 CPU 飈高,內存溢出,頻繁 GC 等等,這些都是使人頭疼的問題。樓主一樣也遇到過這些問題,那麼,遇到這些問題該如何解決呢?
首先,出現問題,確定要先定位問題所在,而後分析問題緣由,再而後解決問題,最後進行總結,防止下次再次出現。
今天的文章,就如咱們的題目同樣,講的是基本操做,也就是一些排查線上問題的基本方法。爲何這麼說呢?由於線上問題千奇百怪,就算是身經百戰的專家也會遇到棘手的問題,所以不可能在一篇文章裏說完,還有一個最重要的緣由,固然就是樓主的水平不到位。
但不到位歸不到位,任何經驗都是值得記錄的,所以,樓主有必要將這些問題記錄一下。
CPU 飈高
線上 CPU 飈高問題你們應該都遇到過,那麼如何定位問題呢?
思路:首先找到 CPU 飈高的那個 Java 進程,由於你的服務器會有多個 JVM 進程。而後找到那個進程中的 「問題線程」,最後根據線程堆棧信息找到問題代碼。最後對代碼進行排查。
-
經過 top 命令找到 CPU 消耗最高的進程,並記住進程 ID。
-
再次經過 top -Hp [進程 ID] 找到 CPU 消耗最高的線程 ID,並記住線程 ID.
-
經過 JDK 提供的 jstack 工具 dump 線程堆棧信息到指定文件中。具體命令:jstack -l [進程 ID] >jstack.log。
-
因爲剛剛的線程 ID 是十進制的,而堆棧信息中的線程 ID 是16進制的,所以咱們須要將10進制的轉換成16進制的,並用這個線程 ID 在堆棧中查找。使用 printf "%x\n" [十進制數字] ,能夠將10進制轉換成16進制。
-
經過剛剛轉換的16進制數字從堆棧信息裏找到對應的線程堆棧。就能夠從該堆棧中看出端倪。
從樓主的經驗來看,通常是某個業務死循環沒有出口,這種狀況能夠根據業務進行修復。還有 C2 編譯器執行編譯時也會搶佔 CPU,什麼是 C2編譯器呢?當 Java 某一段代碼執行次數超過10000次(默認)後,就會將該段代碼從解釋執行改成編譯執行,也就是編譯成機器碼以提升速度。而這個 C2編譯器就是作這個的。如何解決呢?項目上線後,能夠先經過壓測工具進行預熱,這樣,等用戶真正訪問的時候,C2編譯器就不會干擾應用程序了。若是是 GC 線程致使的,那麼極有多是 Full GC ,那麼就要進行 GC 的優化。
內存問題排查
說完了 CPU 的問題排查,再說說內存的排查,一般,內存的問題就是 GC 的問題,由於 Java 的內存由 GC 管理。有2種狀況,一種是內存溢出了,一種是內存沒有溢出,但 GC 不健康。
內存溢出的狀況能夠經過加上 -XX:+HeapDumpOnOutOfMemoryError 參數,該參數做用是:在程序內存溢出時輸出 dump 文件。
有了 dump 文件,就能夠經過 dump 分析工具進行分析了,好比經常使用的MAT,Jprofile,jvisualvm 等工具均可以分析,這些工具都可以看出究竟是哪裏溢出,哪裏建立了大量的對象等等信息。
一般一個健康的 GC 是什麼狀態呢?根據樓主的經驗,YGC 5秒一次左右,每次不超過50毫秒,FGC 最好沒有,CMS GC 一天一次左右。
咱們看YGC,首先看頻率,若是 YGC 超過5秒一次,甚至更長,說明系統內存過大,應該縮小容量,若是頻率很高,說明 Eden 區太小,能夠將 Eden 區增大,但整個新生代的容量應該在堆的 30% - 40%之間,eden,from 和 to 的比例應該在 8:1:1左右,這個比例可根據對象晉升的大小進行調整。
若是 YGC 時間過長呢?YGC 有2個過程,一個是掃描,一個是複製,一般掃描速度很快,複製速度相比而言要慢一些,若是每次都有大量對象要複製,就會將 STW 時間延長,還有一個狀況就是 StringTable ,這個數據結構中存儲着 String.intern 方法返回的常連池的引用,YGC 每次都會掃描這個數據結構(HashTable),若是這個數據結構很大,且沒有通過 FGC,那麼也會拉長 STW 時長,還有一種狀況就是操做系統的虛擬內存,當 GC 時正巧操做系統正在交換內存,也會拉長 STW 時長。
再來看看FGC,實際上,FGC 咱們只能優化頻率,沒法優化時長,由於這個時長沒法控制。如何優化頻率呢?
5 是CMS Promotion failed 或者 concurrent mode failure,
6 JVM 基於悲觀策略認爲此次 YGC 後 Old 區沒法容納晉升的對象,所以取消 YGC,提早 FGC。
一般優化的點是 Old 區內存不夠致使 FGC。若是 FGC 後還有大量對象,說明 Old 區太小,應該擴大 Old 區,若是 FGC 後效果很好,說明 Old 區存在了大量短命的對象,優化的點應該是讓這些對象在新生代就被 YGC 掉,一般的作法是增大新生代,若是有大而短命的對象,經過參數設置對象的大小,不要讓這些對象進入 Old 區,還須要檢查晉升年齡是否太小。若是 YGC 後,有大量對象由於沒法進入 Survivor 區從而提早晉升,這時應該增大 Survivor 區,但不宜太大。
上面說的都是優化的思路,咱們也須要一些工具知道 GC 的情況。
JDK 提供了不少的工具,好比 jmap ,jcmd 等,oracle 官方推薦使用 jcmd 代替 jmap,由於 jcmd 確實能代替 jmap 不少功能。jmap 能夠打印對象的分佈信息,能夠 dump 文件,注意,jmap 和 jcmd dump 文件的時候會觸發 FGC ,使用的時候注意場景。
還有一個比較經常使用的工具是 jstat,該工具能夠查看GC 的詳細信息,好比eden ,from,to,old 等區域的內存使用狀況。
還有一個工具是 jinfo,該工具能夠查看當前 jvm 使用了哪些參數,而且也能夠在不停機的狀況下修改參數。
包括咱們上面說的一些分析 dump 文件的可視化工具,MAT,Jprofile,jvisualvm 等,這些工具能夠分析 jmap dump 下來的文件,看看哪一個對象使用的內存較多,一般是可以查出問題的。
還有很重要的一點就是,線上環境必定要帶上 GC 日誌!!!
總結
基於文章的標題,咱們這個是基本操做,故障排查是說不完的話題,每一個故障涉及的知識也都不少,所以,咱們在學習了基本的排查以後,還須要學習更多事故排查技術,好比排查 IO,網絡,TCP 鏈接等等。樓主將在後面的文章中將這些基本操做都記錄下來。
最後
歡迎你們關注個人公衆號【程序員追風】,文章都會在裏面更新,整理的資料也會放在裏面。