經過使用 JVM 自帶的工具 jstack,jmap,jstat 排查java程序佔用內存或cpu負載太高的問題。html
理論部分java
jstack - Prints Java thread stack traces for a Java process, core file, or remote debug server.linux
打印線程堆棧信息centos
命令行使用:服務器
jstack <pid> 獲取到 jvm 進程的全部堆棧信息網絡
jmap - Prints shared object memory maps or heap memory details for a process, core file, or remote debug server.oracle
打印內存中的對象以及堆內存信息eclipse
命令行使用:jvm
jmap -heap <pid> 獲取 jvm 進程的堆內存配置以及使用狀況工具
jmap -histo <pid> 獲取 jvm 進程的在內存中建立的對象數量
jmap -dump:format=b,file=heap.bin <pid> 導出內存文件,這個文件能夠經過導入eclipse的MemoryAnalyzer來分析
jstat - Monitors Java Virtual Machine (JVM) statistics.
監控 jvm 獲取一些統計信息,classloader,compiler,gc相關
命令行使用:
jstat [option] <pid>
參數列表(上面那個 option):
-class:統計class loader行爲信息
-compile:統計編譯行爲信息
-gc:統計jdk gc時heap信息
-gccapacity:統計不一樣的generations(不知道怎麼翻譯好,包括新生區,老年區,permanent區)相應的heap容量狀況
-gccause:統計gc的狀況,(同-gcutil)和引發gc的事件
-gcnew:統計gc時,新生代的狀況
-gcnewcapacity:統計gc時,新生代heap容量
-gcold:統計gc時,老年區的狀況
-gcoldcapacity:統計gc時,老年區heap容量
-gcpermcapacity:統計gc時,permanent區heap容量
-gcutil:統計gc時,heap狀況
輸出內容:
S0 — Heap上的 Survivor space 0 區已使用空間的百分比
S0C:S0當前容量的大小
S0U:S0已經使用的大小
S1 — Heap上的 Survivor space 1 區已使用空間的百分比
S1C:S1當前容量的大小
S1U:S1已經使用的大小
E — Heap上的 Eden space 區已使用空間的百分比
EC:Eden space當前容量的大小
EU:Eden space已經使用的大小
O — Heap上的 Old space 區已使用空間的百分比
OC:Old space當前容量的大小
OU:Old space已經使用的大小
P — Perm space 區已使用空間的百分比
OC:Perm space當前容量的大小
OU:Perm space已經使用的大小
YGC — 從應用程序啓動到採樣時發生 Young GC 的次數
YGCT– 從應用程序啓動到採樣時 Young GC 所用的時間(單位秒)
FGC — 從應用程序啓動到採樣時發生 Full GC 的次數
FGCT– 從應用程序啓動到採樣時 Full GC 所用的時間(單位秒)
GCT — 從應用程序啓動到採樣時用於垃圾回收的總時間(單位秒),它的值等於YGC+FGC
實戰部分
線上環境存在一臺服務器(centos 7)cpu使用率一直處於報警狀態。
在使用上面3個命令的時候若是出現
Error attaching to process: sun.jvm.hotspot.runtime.VMVersionMismatchException: Supported vertion are 25.65-b01. Target VM is 25.121-b13
說明工具的版本和啓動程序的jvm版本不一致,去oracle官網下載對應版本的jdk工具 傳送門,我使用的是jdk1.8 紅色部分是須要下載的小的版本 號 即jdk1.8_121
使用 top 命令查看cpu使用率
發現一個 pid 爲 30996 的cpu使用率特別高,再次使用 top -Hp 30996 命令查看這個進程的線程使用狀況
31036 這個就是有問題的線程,使用 printf "%x\n" 31036 轉換成16進制(下面有用)
接着就可使用 jstack 命令查看這個線程的堆棧狀況 jstack 30996 | grep -A 10 793c (30996是剛纔查到有問題的進程 793c爲16進制線程號 grep -A 10 爲查到793c那一行以後向下再打印10行)
這個 nid=0x793c 的 Thread-14 線程就是問題線程,屢次執行命令查看以後發現代碼一直運行在 MasterEngine.java 48行。而後就能夠去查看代碼了。
看上去在迭代一個map,循環刪除。
再看下其餘地方
執行 jmap -histo 30996 | head -n 20 看下內存中建立類top20的狀況
連續調用4次發現 java.util.concurrent.ConcurrentHashMap$EntryIterator 實例數量在不斷增大而後忽然又變小了。猜想是實例被 gc 快速回收掉了。
那麼再用 jstat -gcutil 30996 2000 20 看下 gc 執行狀況 (2000 200 是指每隔2秒執行一次 一共執行20次)
看YGC這一列(從右邊往左第五列),young gc 執行了 2千8百萬屢次。
再用 gccapacity 參數看一下堆的狀況
只有Eden space (第6列)的大小在發生變化
再看下 jvm 堆的配置狀況 jmap -heap 30996
配置了最大堆大小(MaxHeapSize)是 4g,Eden Space 45%, From Space 18%,To Space 0%,Old Generation 73%
再看下服務器負載狀況,命令就是一個 w
最後三個數字表示服務器最近 1分鐘 5分鐘 15分鐘 的平均負載,超過cpu核數*1(4核就是4 8核就是8)表示服務器負載很是大。如今這個數字看上去服務器負載比較小仍是可以應對。
總結
結合以上各類跡象,基本能夠判定問題就出在對map進行迭代刪除的代碼,頻繁建立 ConcurrentHashMap$EntryIterator 實例,頻繁觸發 young gc,是致使 cpu 佔用太高的緣由。堆大小的佔用狀況還能夠,負載還能接受,內存硬盤都正常(忘記截圖了),full gc 次數沒有特別多。更加具體的緣由還須要瞭解代碼做者的用意。
因此,只要會使用 jmap jstack jstat 這3個工具,還有一些linux的基本命令(檢查磁盤 網絡 等等),就能夠從容應對由於 代碼寫得太爛 形成程序異常的狀況。