JVM 線上故障排查基本操作

鏈接:https://www.jianshu.com/p/bca5a49db4b7

前言

對於後端程序員,特別是 Java 程序員來講,排查線上問題是不可避免的。各種 CPU 飈高,內存溢出,頻繁 GC 等等,這些都是令人頭疼的問題。樓主同樣也遇到過這些問題,那麼,遇到這些問題該如何解決呢?

首先,出現問題,肯定要先定位問題所在,然後分析問題原因,再然後解決問題,最後進行總結,防止下次再次出現。

今天的文章,就如我們的題目一樣,講的是基本操作,也就是一些排查線上問題的基本方法。為什麼這麼說呢?因為線上問題千奇百怪,就算是身經百戰的專家也會遇到棘手的問題,因此不可能在一篇文章裡說完,還有一個最重要的原因,當然就是樓主的水平不到位。

但不到位歸不到位,任何經驗都是值得記錄的,因此,樓主有必要將這些問題記錄一下。

還有,本文的排查環境是 Linux。

CPU 飈高

線上 CPU 飈高問題大家應該都遇到過,那麼如何定位問題呢?

思路:首先找到 CPU 飈高的那個 Java 進程,因為你的服務器會有多個 JVM 進程。然後找到那個進程中的 “問題線程”,最後根據線程堆棧信息找到問題代碼。最後對代碼進行排查。

如何操作呢?

  1. 通過 top 命令找到 CPU 消耗最高的進程,並記住進程 ID。
  2. 再次通過 top -Hp [進程 ID] 找到 CPU 消耗最高的線程 ID,並記住線程 ID.
  3. 通過 JDK 提供的 jstack 工具 dump 線程堆棧信息到指定文件中。具體命令:jstack -l [進程 ID] >jstack.log。
  4. 由於剛剛的線程 ID 是十進制的,而堆棧信息中的線程 ID 是16進制的,因此我們需要將10進制的轉換成16進制的,並用這個線程 ID 在堆棧中查找。使用 printf "%x\\n" [十進制數字] ,可以將10進制轉換成16進制。
  5. 通過剛剛轉換的16進制數字從堆棧信息裡找到對應的線程堆棧。就可以從該堆棧中看出端倪。

從樓主的經驗來看,一般是某個業務死循環沒有出口,這種情況可以根據業務進行修復。還有 C2 編譯器執行編譯時也會搶佔 CPU,什麼是 C2編譯器呢?當 Java 某一段代碼執行次數超過10000次(默認)後,就會將該段代碼從解釋執行改為編譯執行,也就是編譯成機器碼以提高速度。而這個 C2編譯器就是做這個的。如何解決呢?項目上線後,可以先通過壓測工具進行預熱,這樣,等用戶真正訪問的時候,C2編譯器就不會干擾應用程序了。如果是 GC 線程導致的,那麼極有可能是 Full GC ,那麼就要進行 GC 的優化。

JVM 線上故障排查基本操作

內存問題排查

說完了 CPU 的問題排查,再說說內存的排查,通常,內存的問題就是 GC 的問題,因為 Java 的內存由 GC 管理。有2種情況,一種是內存溢出了,一種是內存沒有溢出,但 GC 不健康。

內存溢出的情況可以通過加上 -XX:+HeapDumpOnOutOfMemoryError 參數,該參數作用是:在程序內存溢出時輸出 dump 文件。

有了 dump 文件,就可以通過 dump 分析工具進行分析了,比如常用的MAT,Jprofile,jvisualvm 等工具都可以分析,這些工具都能夠看出到底是哪裡溢出,哪裡創建了大量的對象等等信息。

第二種情況就比較複雜了。GC 的健康問題。

通常一個健康的 GC 是什麼狀態呢?根據樓主的經驗,YGC 5秒一次左右,每次不超過50毫秒,FGC 最好沒有,CMS GC 一天一次左右。

而 GC 的優化有2個維度,一是頻率,二是時長。

我們看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 我們只能優化頻率,無法優化時長,因為這個時長無法控制。如何優化頻率呢?

首先,FGC 的原因有幾個,1 是 Old 區內存不夠,2 是元數據區內存不夠,3 是 System.gc(), 4 是 jmap 或者 jcmd,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 連接等等。


分享到:


相關文章: