1. 程式人生 > >JVM除錯常用命令——jmap、jstat(2)

JVM除錯常用命令——jmap、jstat(2)

==========
(接上文《JVM除錯常用命令——jps、jinfo(1)》)

1.3、jmap命令(及配套的jhat)

jmap命令最大的作用是生成當前指定的Java程序的dump檔案,這個dump檔案有利於在正式生成環境下觀察JVM執行的詳細情況,這樣才能著手找出可能的問題。jmap命令還可以查詢finalize執行佇列、Java堆的詳細資訊,例如檢視新生代目前使用的是哪種收集器。

1.3.1、jmap命令概要

jmap命令的使用格式如下所示:

# jmap --help
Usage:
    jmap [option] <pid>
        (
to connect to running process) jmap [option] <executable <core> (to connect to a core file) jmap [option] [[email protected]]<remote server IP or hostname> (to connect to remote debug server) where <option> is one of: <none> to print same info as Solaris pmap -heap to print java heap summary -histo[
:live] to print histogram of java object heap; if the "live" suboption is specified, only count live objects -clstats to print class loader statistics -finalizerinfo to print information on objects awaiting finalization -dump:<dump-options>
to dump java heap in hprof binary format dump-options: live dump only live objects; if not specified, all objects in the heap are dumped. format=b binary format file=<file> dump heap to <file> Example: jmap -dump:live,format=b,file=heap.bin <pid> -F force. Use with -dump:<dump-options> <pid> or -histo to force a heap dump or histogram when <pid> does not respond. The "live" suboption is not supported in this mode. -h | -help to print this help message -J<flag> to pass <flag> directly to the runtime system

jmap可以使用如上“jmap [option] [[email protected]]<remote server IP or hostname>”的命令格式監控遠端伺服器上的java程序資訊,但是其最重要的還是option引數,我們首先介紹option引數的具體意義:

  • -<none> 這個意思是說,jmap可以不加任何option引數資訊,只是指定Java程序的程序號。這種情況下,jmap命令將按照Linux作業系統程序記憶體分析命令pmap的相關性,輸出記憶體分析結果。

  • -heap 改引數將輸出當前指定java程序的堆記憶體概要資訊。

  • -clstats 該引數將打印出當前java程序中,存在的每個類載入器,以及通過該類載入器已經完成載入的各類資訊,包括但不限於類載入器的活動情況、已經載入的類數量、關聯的父類載入器等等(class檔案通過類載入器完成的載入、連線、驗證初始化等過程可以在這個命令的輸出詳情中具體體現出來)。

  • finalizerinfo 該引數可打印出等待終結的物件資訊,當Java程序在頻繁進行Full GC的時候,可以通過該命令獲取問題的排查依據。

  • -histo[:live] 該引數可以輸出每個class的例項數目、記憶體佔用、類全名等資訊。如果live子引數加上後,只統計活的物件數量。該命令非常有用,舉個例子,你可以使用該名了檢查軟體系統的某種設計模式是否符合設計預期。

  • -dump:<dump-options> 該引數代表了jmap最重要的功能,取得當前指定java程序堆記憶體中各個class例項的詳細資訊,並輸出到指定檔案。dump命令還有三個子引數分別是。live只分析輸出目前有活動例項的class資訊;format輸出格式,預設為“b”,可以使用配套的分析軟體進行分析;file子引數可以指定輸出的檔案,注意,如果輸出檔案已經存在,則可以使用-F 引數來強制執行命令。

1.3.2、jmap命令效果演示

下面我們來實際演示一下,jmap攜帶各種引數後的執行效果。首先使用一個jmap命令,打印出當前堆記憶體的概要資訊,示例如下:

# jmap -heap 55236
.........這裡省略一些資訊
JVM version is 25.144-b01
using thread-local object allocation.
Parallel GC with 8 thread(s)
Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 4213178368 (4018.0MB)
   NewSize                  = 88080384 (84.0MB)
   MaxNewSize               = 1404043264 (1339.0MB)
   OldSize                  = 176160768 (168.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
   capacity = 691011584 (659.0MB)
   used     = 587018448 (559.8244171142578MB)
   free     = 103993136 (99.17558288574219MB)
   84.95059440277053% used
From Space:
   capacity = 24117248 (23.0MB)
   used     = 0 (0.0MB)
   free     = 24117248 (23.0MB)
   0.0% used
To Space:
   capacity = 23592960 (22.5MB)
   used     = 0 (0.0MB)
   free     = 23592960 (22.5MB)
   0.0% used
PS Old Generation
   capacity = 167247872 (159.5MB)
   used     = 43639424 (41.6177978515625MB)
   free     = 123608448 (117.8822021484375MB)
   26.09266322981975% used
30578 interned Strings occupying 3522224 bytes.

通過打印出來的堆概要資訊,我們大致可以得到包括以下描述在內的關鍵資訊:JVM使用Parallel GC垃圾回收器,這是一種吞吐量優先的,年輕代和年老代都可以使用的並行回收器。另外我們可以看到,年輕代Eden區域的容量為659.0MB,其中已經使用了84.9%(看來不久的將來就會進行一次基於複製演算法的回收動作)……

我們還可以通過以下命令,觀察指定的Java程序歷史上和目前存在的類載入器資訊。如下所示:

# jmap -clstats 13518
Attaching to process ID 13518, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11
finding class loader instances ..done.
computing per loader stat ..done.
please wait.. computing liveness.....liveness analysis may be inaccurate ...
class_loader    classes bytes   parent_loader   alive?  type
<bootstrap>     2687    4562634   null          live    <internal>
0x00000006c7c4c7c0      1       1473    0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x000000078aa6e2a0      1       880     0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x00000006c9c78dc0      1       880     0x00000006c7c2af10      dead    sun/reflect/[email protected]
………………節約篇幅,這裡省去一些資訊                                                                                  c0767d98
0x00000006c9c937a0      1       1472    0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x00000006c7c4cba8      1       1476    0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x00000006c9c791a8      1       880     0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x00000006c9c933b8      1       1485    0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x00000006c8da34a8      1       880     0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x00000006c7c4b9b0      1       880     0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x000000078a951ce0      1       880     0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x000000078aaf6ad8      1       880     0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x00000006c8da46a0      1       880     0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x00000006c9c79fb8      1       880     0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x000000078aa80428      1       880     0x00000006c7c2af10      dead    sun/reflect/[email protected]
………………節約篇幅,這裡省去一些資訊                                                                                  c0767d98
0x000000078752ef98      1       880       null          dead    sun/reflect/[email protected]
0x00000006c7c4d508      1       1472    0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x000000078a953658      1       880     0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x00000006c9c79b08      1       880     0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x0000000788887258      1       880     0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x0000000788891658      1       880     0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x00000006c7c2af10      10148   15865581        0x00000006c7c1cab0      live    org/springframework/boot/loader/LaunchedURLClass                                                                                                             [email protected]
0x00000006c8da3e08      1       1472    0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x00000007888a1c58      1       880     0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x000000078aa38470      1       880     0x00000006c7c2af10      dead    sun/reflect/[email protected]
………………節約篇幅,這裡省去一些資訊                                                                                  c0767d98
0x00000006c8da5320      1       1472      null          dead    sun/reflect/[email protected]
0x000000078aa4cb58      1       880     0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x0000000788ddab20      1       880     0x00000006c7c2af10      dead    sun/reflect/[email protected]
0x000000078a950368      1       880     0x00000006c7c2af10      dead    sun/reflect/[email protected]

total = 289     13459   21804150            N/A         alive=16, dead=273          N/A

通過以上命令示例我們可以看到,目前指定的Java程序一共有289個類載入器,其中已失效273個,目前存活16個,以上結果中有一個名叫<bootstrap>的類載入器,這個就是我們常說來的Bootstrap ClassLoader載入器,可以看到通過這個載入器完成的class檔案載入是最多的,且它之上在沒有父級載入器了。

接下來在做一個示例,通過-histo:live引數檢查目前系統中各個class的例項載入數量以及當前佔用的記憶體情況。請注意,由於“jmap -histo:live”命令輸出的內容非常多,所以一般來說我們會將這個命令的輸出轉存到指定的檔案中,再進行排查。命令示例如下:

# jmap -histo:live 13518 >> ./histo_live
// 接下來可以使用vim命令,檢視輸出情況,並進行分析
# vim ./histo_live
 num     #instances         #bytes  class name
----------------------------------------------
   1:         83445       12138320  [C
   2:         24921        2193048  java.lang.reflect.Method
   3:         81976        1967424  java.lang.String
   4:         53592        1714944  java.util.concurrent.ConcurrentHashMap$Node
   5:          6321        1502344  [I
   6:         13505        1500784  java.lang.Class
   7:          2457        1079936  [B
   8:         19475         779000  java.util.LinkedHashMap$Entry
   9:         14483         719080  [Ljava.lang.Object;
  10:         21744         695808  java.lang.ref.WeakReference
  11:          8495         669560  [Ljava.util.HashMap$Node;
  12:         20190         646080  java.util.HashMap$Node
  13:         36296         580736  java.lang.Object
  14:         10217         572152  java.util.LinkedHashMap
  15:           314         471296  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  16:         10221         408840  java.lang.ref.SoftReference
  17:         17904         384440  [Ljava.lang.Class;
  18:         12172         292128  java.beans.MethodRef
  19:          8286         256704  [Ljava.lang.String;
  20:          4320         241920  java.beans.MethodDescriptor
  21:          9191         220584  java.util.ArrayList
  22:          7699         184776  org.springframework.core.MethodClassKey
  23:          5385         172320  java.util.LinkedList
  24:          1700         163200  org.springframework.beans.GenericTypeAwarePropertyDescriptor
  25:          2224         160128  java.beans.PropertyDescriptor
  26:          3291         157968  java.util.HashMap
  27:          1766         141280  java.lang.reflect.Constructor
  28:          1463         105336  java.lang.reflect.Field
  29:            91          87096  [J
  30:          3569          85656  java.util.LinkedList$Node
  31:          1104          79488  org.springframework.core.annotation.AnnotationAttributes
  32:          1929          77160  java.util.TreeMap$Entry
  33:          1189          76096  org.springframework.core.MethodParameter
  34:          4548          72768  java.lang.Integer
  35:          4410          70560  java.util.LinkedHashSet
  …………………… 後面的內容還有很多,為了節約篇幅就省略了。

接下來你就可以使用vim中的檢視命令,檢查你需要觀察的相關class的例項數量。前文已經說到,使用jmap最主要的功能是生成JVM堆記憶體中class例項化物件的各種詳細資訊的Dump檔案(實際上就是以上各種jmap命令形態輸出結果的Dump資訊表現),以下命令使用示例就可以做這個事情,如下:

# jmap -dump:live,format=b,file=./dump 13518
Dumping heap to /root/dump ...
Heap dump file created

1.3.4、jmap配套使用的jhat命令

請注意,使用“jmap -dump”命令生成的dump檔案是一種二進位制格式的表達,你用vim命令(或者相似命令)是無法看到分析結果的。這裡就要介紹另外一種配套命令jhat了。通過jhat命令,我們可以基於jmap的dump檔案啟動一個Http服務,並在瀏覽器上檢查dump資訊。命令示例如下:

# jhat -port 5700 ./dump
Reading from ./dump...
Dump file created Mon Nov 19 14:46:03 CST 2018
Snapshot read, resolving...
Resolving 631850 objects...
Chasing references, expect 126 dots.......................................................
Eliminating duplicate references................................................................................
Snapshot resolved.
Started HTTP server on port 5700
Server is ready.

可以看到,服務已經啟動成功了,接下來我們可以通過瀏覽器觀察dump檔案的詳細資訊,如下圖所示:

在這裡插入圖片描述

jhat 命令非常簡單,限於本文篇幅這裡就不再具體介紹了,有興趣的朋友可以查閱各種網路資料進行詳細瞭解(這裡要說明一下,其中有一個OQL物件查詢語言,很有意思)。

1.4、jstat命令

1.4.1、基本引數介紹

jstat命令可以檢視堆記憶體各部分的使用量,以及載入類的數量。命令格式如下:

Usage: jstat -help|-options
       jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

Definitions:
  <option>      An option reported by the -options option
  <vmid>        Virtual Machine Identifier. A vmid takes the following form:
                     <lvmid>[@<hostname>[:<port>]]
                Where <lvmid> is the local vm identifier for the target
                Java virtual machine, typically a process id; <hostname> is
                the name of the host running the target Java virtual machine;
                and <port> is the port number for the rmiregistry on the
                target host. See the jvmstat documentation for a more complete
                description of the Virtual Machine Identifier.
  <lines>       Number of samples between header lines.
  <interval>    Sampling interval. The following forms are allowed:
                    <n>["ms"|"s"]
                Where <n> is an integer and the suffix specifies the units as
                milliseconds("ms") or seconds("s"). The default units are "ms".
  <count>       Number of samples to take before terminating.
  -J<flag>      Pass <flag> directly to the runtime system.

請注意以上使用示例中的option引數,在jstat命令基本的幫助資訊中,並沒有對option支援的引數情況進行說明,但是讀者可以使用以下命令,列出jstat命令可以使用的option引數:

# jstat -options
-class # 顯示載入class的數量,及所佔空間等資訊
-compiler # 主要顯示JIT編譯器已經編譯過的方法、耗時等資訊
-gc # 可以顯示詳細的gc資訊,檢視gc的次數,這個引數代表的各個顯示列,將在後文中進行詳細說明
-gccapacity # 可以顯示JVM各個記憶體結構中的容量情況,該引數也將在後文中進行詳細說明
-gcnew # 可以顯示新生代中的GC情況
-gcnewcapacity # 顯示新生代中和容量有關的各種情況
-gcold # 可以顯示年老代中的GC情況
-gcoldcapacity # 顯示年老代中和容量有關的各種情況
-gccause # 可以顯示最近一次GC的原因
-gcmetacapacity # 可以顯示有關元資料空間大小的統計資訊
-gcutil # 主要監控GC情況中,已使用容量和總容量的各種百分比情況
-printcompilation # 輸入已經被JIT編譯過的方法

jstat命令非常重要,有了以上jstat命令的基本引數介紹,我們就可以開始正式使用jstat命令了。以下是一個使用-gc引數時的簡單示例(監控指定程序的GC回收情況):

// 以下命令中,84256代表指定的java程序號、1000代表每一秒(1000毫秒)輸出一次監控資訊
# jstat -gc 84256  1000
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
10752.0 10752.0  0.0    0.0   64512.0  35554.9   172032.0     0.0     4480.0 781.2  384.0   75.9       0    0.000   0      0.000    0.000
10752.0 10752.0  0.0    0.0   64512.0  35554.9   172032.0     0.0     4480.0 781.2  384.0   75.9       0    0.000   0      0.000    0.000

接著再來一個簡單示例(檢視指定程序中年輕代的GC情況):

jstat -gcnew 84256  1000
 S0C    S1C    S0U    S1U   TT MTT  DSS      EC       EU     YGC     YGCT
10752.0 10752.0    0.0    0.0 15  15    0.0  64512.0  35554.9      0    0.000
10752.0 10752.0    0.0    0.0 15  15    0.0  64512.0  35554.9      0    0.000
10752.0 10752.0    0.0    0.0 15  15    0.0  64512.0  35554.9      0    0.000

再來一個簡單示例(檢視GC操作下個記憶體結構中的容量情況):

jstat -gccapacity  84256 1000
 NGCMN    NGCMX     NGC     S0C   S1C       EC      OGCMN      OGCMX       OGC         OC       MCMN     MCMX      MC     CCSMN    CCSMX     CCSC    YGC    FGC
 86016.0 1371136.0  86016.0 10752.0 10752.0  64512.0   172032.0  2743296.0   172032.0   172032.0      0.0 1056768.0   4480.0      0.0 1048576.0    384.0      0     0
 86016.0 1371136.0  86016.0 10752.0 10752.0  64512.0   172032.0  2743296.0   172032.0   172032.0      0.0 1056768.0   4480.0      0.0 1048576.0    384.0      0     0
 86016.0 1371136.0  86016.0 10752.0 10752.0  64512.0   172032.0  2743296.0   172032.0   172032.0      0.0 1056768.0   4480.0      0.0 1048576.0    384.0      0     0

最後再來一個示例(監控GC情況中,已使用容量和總容量的各種百分比情況):

# jstat -gcutil 84256 1000
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
  0.00   0.00  55.11   0.00  17.44  19.76      0    0.000     0    0.000    0.000
  0.00   0.00  55.11   0.00  17.44  19.76      0    0.000     0    0.000    0.000
  0.00   0.00  55.11   0.00  17.44  19.76      0    0.000     0    0.000    0.000

1.4.2、jstat列名介紹

額,各位讀者是不是覺得完全一頭霧水,以上“EC”、“EU”等列名代表什麼意義呢?這裡我們進行一個比較詳細的列明介紹(單位都是位元組):

列名項 列含義說明 列名項 列含義說明
E 年輕代中Eden(伊甸園)已使用的佔當前容量百分比 O 年老代已使用的佔當前容量百分比
M 元空間(MetaspaceSize)已使用的佔當前容量百分比 P 持久代已使用的佔當前容量百分比 (這個引數在JDK 1.8之前才有效)
S0 年輕代中第一個survivor(倖存區)已使用的佔當前容量百分比 S1 年輕代中第二個survivor(倖存區)已使用的佔當前容量百分比
S0C 年輕代中第一個survivor(倖存區)的容量 S1C 年輕代中第二個survivor(倖存區)的容量
S0U 年輕代中第一個survivor(倖存區)目前已使用空間 S1U 年輕代中第二個survivor(倖存區)目前已使用空間
EC 年輕代中Eden(伊甸園)的容量 EU 年輕代中Eden(伊甸園)目前已使用空間
OC 年老代的容量 OU 年老代目前已使用空間
MC 當前元資料空間大小 MU 當前元資料空間已使用大小
CCS 壓縮使用比例
CCSC 當前壓縮類空間大小 CCSU 當前壓縮類空間已使用大小
YGC 年輕代垃圾回收次數 YGCT 年輕代垃圾回收消耗時間
FGC 老年代GC次數 FGCT 老年代垃圾回收消耗時間
TT 持有次數限制 MTT 最大持有次數限制
GCT 垃圾回收消耗總時間 DSS 當前需要survivor(倖存區)的容量 (位元組)(Eden區已滿)
NGCMN 年輕代(young)中初始化(最小)的大小 NGCMX 年輕代(young)的最大容量
NGC 年輕代(young)中當前的容量
OGCMN 年老代中初始化(最小)的大小 OGCMX 年老代的最大容量
OGC 年老代當前新生成的容量
MCMN 最小元資料容量 MCMX 最大元資料容量
CCSMN 最小壓縮類空間大小 CCSMX 最大壓縮類空間大小

=========================
(接下文)