JVM系列(七) - JVM線上監控工具


前言

通過上一篇的 JVM 垃圾回收知識,我們了解了 JVM 具體的 垃圾回收算法 和幾種 垃圾回收器。理論是指導實踐的工具,有了理論指導,定位問題的時候,知識和經驗是關鍵基礎,數據可以為我們提供依據。

在線上我們經常會遇見如下幾個問題:

  • 內存泄露;
  • 某個進程突然 CPU 飆升;
  • 線程死鎖;
  • 響應變慢。

如果遇到了以上這種問題,在 線下環境 可以有各種 可視化的本地工具 支持查看。但是一旦到 線上環境,就沒有這么多的 本地調試工具 支持,我們該如何基於 監控工具 來進行定位問題?

我們一般會基於 數據收集 來定位問題,而數據的收集離不開 監控工具 的處理,比如:運行日志異常堆棧GC 日志線程快照堆內存快照 等。為了解決以上問題,我們常用的 JVM 性能調優監控工具 大致有:jpsjstatjstackjmapjhathprofjinfo

正文

如果想要查看 Java 進程中 線程堆棧 的信息,可以選擇 jstack 命令。如果要查看 堆內存,可以使用 jmap 導出並使用 jhat 來進行分析,包括查看 類的加載信息GC 算法對象 的使用情況等。可以使用 jstat 來對 JVM 進行 統計監測,包括查看各個 區內存 和 GC 的情況,還可以使用 hprof 查看 CPU 使用率,統計 堆內存 使用情況。下面會詳細的介紹這幾個工具的用法。

JVM常見監控工具 & 指令

1. jps進程監控工具

jps 是用於查看有權訪問的 hotspot 虛擬機 的進程。當未指定 hostid 時,默認查看 本機 jvm 進程,否則查看指定的 hostid 機器上的 jvm 進程,此時 hostid 所指機器必須開啟 jstatd 服務。

jps 可以列出 jvm 進程 lvmid主類類名main 函數參數, jvm 參數,jar 名稱等信息。

命令格式如下:

1
2
3
4
5
usage: jps [-help]
jps [-q] [-mlvV] [<hostid>]

Definitions:
<hostid>: <hostname>[:<port>]

參數含義如下:

  • -q: 不輸出 類名稱Jar 名稱 和傳入 main 方法的 參數
  • -l: 輸出 main 類或 Jar 的 全限定名稱
  • -m: 輸出傳入 main 方法的 參數
  • -v: 輸出傳入 JVM 的參數。

2. jinfo配置信息查看工具

jinfoJVM Configuration info)這個命令作用是實時查看和調整 虛擬機運行參數。之前的 jps -v 命令只能查看到顯示 指定的參數,如果想要查看 未顯示 的參數的值就要使用 jinfo 命令。

1
2
3
4
5
6
7
Usage:
jinfo [option] <pid>
(to connect to running process)
jinfo [option] <executable <core>
(to connect to a core file)
jinfo [option] [server_id@]<remote server IP or hostname>
(to connect to remote debug server)

參數含義如下:

  • pid:本地 jvm 服務的進程 ID
  • executable core:打印 堆棧跟蹤 的核心文件;
  • remote server IP/hostname:遠程 debug 服務的 主機名 或 IP 地址;
  • server id:遠程 debug 服務的 進程 ID

參數選項說明如下:

參數 參數含義
flag 輸出指定 args 參數的值
flags 不需要 args 參數,輸出所有 JVM 參數的值
sysprops 輸出系統屬性,等同於 System.getProperties()
  • 查看正在運行的 jvm 進程的 擴展參數
1
2
3
4
5
6
7
$ jinfo -flags 31983 
Attaching to process ID 31983, please wait…
Debugger attached successfully.
Server compiler detected.
JVM version is 25.91-b14
Non-default VM flags: -XX:CICompilerCount=3 -XX:InitialHeapSize=20971520 -XX:MaxHeapFreeRatio=90 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=2097152 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=2097152 -XX:OldSize=18874368 -XX:+PrintGC -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
Command line: -Xmx20m -Xms20m -Xmn2m -javaagent:/opt/idea-IU-181.4668.68/lib/idea_rt.jar=34989:/opt/idea-IU-181.4668.68/bin -Dfile.encoding=UTF-8
  • 查看正在運行的 jvm 進程的所有 參數信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
$ jinfo 31983
Attaching to process ID 31983, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.91-b14
Java System Properties:

java.runtime.name = Java(TM) SE Runtime Environment
java.vm.version = 25.91-b14
sun.boot.library.path = /opt/jdk1.8.0_91/jre/lib/amd64
java.vendor.url = http://java.oracle.com/
java.vm.vendor = Oracle Corporation
path.separator = :
file.encoding.pkg = sun.io
java.vm.name = Java HotSpot(TM) 64-Bit Server VM
sun.os.patch.level = unknown
sun.java.launcher = SUN_STANDARD
user.country = CN
user.dir = /home/linchen/projects
java.vm.specification.name = Java Virtual Machine Specification
java.runtime.version = 1.8.0_91-b14
java.awt.graphicsenv = sun.awt.X11GraphicsEnvironment
os.arch = amd64
java.endorsed.dirs = /opt/jdk1.8.0_91/jre/lib/endorsed
java.io.tmpdir = /tmp
line.separator =

java.vm.specification.vendor = Oracle Corporation
os.name = Linux
sun.jnu.encoding = UTF-8
java.library.path = /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
java.specification.name = Java Platform API Specification
java.class.version = 52.0
sun.management.compiler = HotSpot 64-Bit Tiered Compilers
os.version = 4.15.0-24-generic
user.home = /home/linchen
user.timezone =
java.awt.printerjob = sun.print.PSPrinterJob
file.encoding = UTF-8
java.specification.version = 1.8
user.name = linchen
java.class.path = /opt/jdk1.8.0_91/jre/lib/charsets.jar:/opt/jdk1.8.0_91/jre/lib/deploy.jar:/opt/jdk1.8.0_91/jre/lib/ext/cldrdata.jar:/opt/jdk1.8.0_91/jre/lib/ext/dnsns.jar:/opt/jdk1.8.0_91/jre/lib/ext/jaccess.jar:/opt/jdk1.8.0_91/jre/lib/ext/jfxrt.jar:/opt/jdk1.8.0_91/jre/lib/ext/localedata.jar:/opt/jdk1.8.0_91/jre/lib/ext/nashorn.jar:/opt/jdk1.8.0_91/jre/lib/ext/sunec.jar:/opt/jdk1.8.0_91/jre/lib/ext/sunjce_provider.jar:/opt/jdk1.8.0_91/jre/lib/ext/sunpkcs11.jar:/opt/jdk1.8.0_91/jre/lib/ext/zipfs.jar:/opt/jdk1.8.0_91/jre/lib/javaws.jar:/opt/jdk1.8.0_91/jre/lib/jce.jar:/opt/jdk1.8.0_91/jre/lib/jfr.jar:/opt/jdk1.8.0_91/jre/lib/jfxswt.jar:/opt/jdk1.8.0_91/jre/lib/jsse.jar:/opt/jdk1.8.0_91/jre/lib/management-agent.jar:/opt/jdk1.8.0_91/jre/lib/plugin.jar:/opt/jdk1.8.0_91/jre/lib/resources.jar:/opt/jdk1.8.0_91/jre/lib/rt.jar:/home/linchen/IdeaProjects/core_java/target/classes:/home/linchen/.m2/repository/io/netty/netty-all/4.1.7.Final/netty-all-4.1.7.Final.jar:/home/linchen/.m2/repository/junit/junit/4.12/junit-4.12.jar:/home/linchen/.m2/repository/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar:/home/linchen/.m2/repository/com/lmax/disruptor/3.3.0/disruptor-3.3.0.jar:/home/linchen/.m2/repository/com/rabbitmq/amqp-client/5.3.0/amqp-client-5.3.0.jar:/home/linchen/.m2/repository/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar:/opt/idea-IU-181.4668.68/lib/idea_rt.jar
java.vm.specification.version = 1.8
sun.arch.data.model = 64
sun.java.command = com.own.learn.jvm.JinfoTest
java.home = /opt/jdk1.8.0_91/jre
user.language = zh
java.specification.vendor = Oracle Corporation
awt.toolkit = sun.awt.X11.XToolkit
java.vm.info = mixed mode
java.version = 1.8.0_91
java.ext.dirs = /opt/jdk1.8.0_91/jre/lib/ext:/usr/java/packages/lib/ext
sun.boot.class.path = /opt/jdk1.8.0_91/jre/lib/resources.jar:/opt/jdk1.8.0_91/jre/lib/rt.jar:/opt/jdk1.8.0_91/jre/lib/sunrsasign.jar:/opt/jdk1.8.0_91/jre/lib/jsse.jar:/opt/jdk1.8.0_91/jre/lib/jce.jar:/opt/jdk1.8.0_91/jre/lib/charsets.jar:/opt/jdk1.8.0_91/jre/lib/jfr.jar:/opt/jdk1.8.0_91/jre/classes
java.vendor = Oracle Corporation
file.separator = /
java.vendor.url.bug = http://bugreport.sun.com/bugreport/
sun.io.unicode.encoding = UnicodeLittle
sun.cpu.endian = little
sun.desktop = gnome
sun.cpu.isalist =

VM Flags:
Non-default VM flags: -XX:CICompilerCount=3 -XX:InitialHeapSize=20971520 -XX:MaxHeapFreeRatio=90 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=2097152 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=2097152 -XX:OldSize=18874368 -XX:+PrintGC -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
Command line: -Xmx20m -Xms20m -Xmn2m -javaagent:/opt/idea-IU-181.4668.68/lib/idea_rt.jar=34989:/opt/idea-IU-181.4668.68/bin -Dfile.encoding=UTF-8
  • 查看正在運行的 jvm 進程的 環境變量信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ jinfo -sysprops 31983 
Attaching to process ID 31983, please wait…
Debugger attached successfully.
Server compiler detected.
JVM version is 25.91-b14
java.runtime.name = Java(TM) SE Runtime Environment
java.vm.version = 25.91-b14
sun.boot.library.path = /opt/jdk1.8.0_91/jre/lib/amd64
java.vendor.url = http://java.oracle.com/
java.vm.vendor = Oracle Corporation
path.separator = :
file.encoding.pkg = sun.io
java.vm.name = Java HotSpot(TM) 64-Bit Server VM
sun.os.patch.level = unknown
sun.java.launcher = SUN_STANDARD
user.country = CN
user.dir = /home/linchen/projects
java.vm.specification.name = Java Virtual Machine Specification
java.runtime.version = 1.8.0_91-b14
java.awt.graphicsenv = sun.awt.X11GraphicsEnvironment
os.arch = amd64
java.endorsed.dirs = /opt/jdk1.8.0_91/jre/lib/endorsed
java.io.tmpdir = /tmp
line.separator =

2. jstat信息統計監控工具

jstat 是用於識別 虛擬機 各種 運行狀態信息 的命令行工具。它可以顯示 本地 或者 遠程虛擬機 進程中的 類裝載內存垃圾收集jit 編譯 等運行數據,它是 線上 定位 jvm 性能 的首選工具。

jstat 工具提供如下的 jvm 監控功能:

  1. 類的加載 及 卸載 的情況;
  2. 查看 新生代老生代 及 元空間MetaSpace)的 容量 及使用情況;
  3. 查看 新生代老生代 及 元空間MetaSpace)的 垃圾回收情況,包括垃圾回收的 次數,垃圾回收所占用的 時間
  4. 查看 新生代 中 Eden 區及 Survior 區中 容量 及 分配情況 等。

命令格式如下:

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

參數含義如下:

  • option: 參數選項。
    • -t: 可以在打印的列加上 timestamp 列,用於顯示系統運行的時間。
    • -h: 可以在 周期性數據 的時候,可以在指定輸出多少行以后輸出一次 表頭
  • vmid: Virtual Machine ID(進程的 pid)。
  • lines: 表頭 與 表頭 的間隔行數。
  • interval: 執行每次的 間隔時間,單位為 毫秒
  • count: 用於指定輸出記錄的 次數,缺省則會一直打印。

參數選項說明如下:

  • class: 顯示 類加載 ClassLoad 的相關信息;
  • compiler: 顯示 JIT 編譯 的相關信息;
  • gc: 顯示和 gc 相關的 堆信息
  • gccapacity: 顯示 各個代 的 容量 以及 使用情況
  • gcmetacapacity: 顯示 元空間 metaspace 的大小;
  • gcnew: 顯示 新生代 信息;
  • gcnewcapacity: 顯示 新生代大小 和 使用情況
  • gcold: 顯示 老年代 和 永久代 的信息;
  • gcoldcapacity: 顯示 老年代 的大小;
  • gcutil: 顯示 垃圾回收信息
  • gccause: 顯示 垃圾回收 的相關信息(同 -gcutil),同時顯示 最后一次 或 當前 正在發生的垃圾回收的 誘因
  • printcompilation: 輸出 JIT 編譯 的方法信息;

2.1. class

顯示和監視 類裝載卸載數量總空間 以及 耗費的時間

1
2
3
$ jstat -class 8615
Loaded Bytes Unloaded Bytes Time
7271 13325.8 1 0.9 2.98

參數列表及含義如下:

參數 參數含義
Loaded 已經裝載的類的數量
Bytes 裝載類所占用的字節數
Unloaded 已經卸載類的數量
Bytes 卸載類的字節數
Time 裝載和卸載類所花費的時間

2.2. compiler

顯示虛擬機 實時編譯JIT)的 次數 和 耗時 等信息。

1
2
3
$ jstat -compiler 8615
Compiled Failed Invalid Time FailedType FailedMethod
3886 0 0 1.29 0

參數列表及含義如下:

參數 參數含義
Compiled 編譯任務執行數量
Failed 編譯任務執行失敗數量
Invalid 編譯任務執行失效數量
Time 編譯任務消耗時間
FailedType 最后一個編譯失敗任務的類型
FailedMethod 最后一個編譯失敗任務所在的類及方法

2.3. gc

顯示 垃圾回收gc)相關的 堆信息,查看 gc 的 次數 及 時間

1
2
3
$ jstat -gc 8615
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
20480.0 10752.0 0.0 0.0 262128.0 130750.7 165376.0 24093.7 35456.0 33931.0 4992.0 4582.0 5 0.056 2 0.075 0.131

比如下面輸出的是 GC 信息,采樣 時間間隔 為 250ms,采樣數為 4

1
2
3
4
5
6
$ jstat -gc 8615 250 4
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
20480.0 10752.0 0.0 0.0 262144.0 130750.7 165376.0 24093.7 35456.0 33931.0 4992.0 4582.0 5 0.056 2 0.075 0.131
20480.0 10752.0 0.0 0.0 262872.0 130750.7 165376.0 24093.7 35456.0 33931.0 4992.0 4582.0 5 0.056 2 0.075 0.131
20480.0 10752.0 0.0 0.0 262720.0 130750.7 165376.0 24093.7 35456.0 33931.0 4992.0 4582.0 5 0.056 2 0.075 0.131
20480.0 10752.0 0.0 0.0 262446.0 130750.7 165376.0 24093.7 35456.0 33931.0 4992.0 4582.0 5 0.056 2 0.075 0.131

參數列表及含義如下:

參數 參數含義
S0C 年輕代中第一個 survivor 的容量
S1C 年輕代中第二個 survivor 的容量
S0U 年輕代中第一個 survivor 目前已使用空間
S1U 年輕代中第二個 survivor 目前已使用空間
EC 年輕代中 Eden 的容量
EU 年輕代中 Eden 目前已使用空間
OC 老年代的容量
OU 老年代目前已使用空間
MC 元空間 metaspace 的容量
MU 元空間 metaspace 目前已使用空間
YGC 從應用程序啟動到采樣時 年輕代 中 gc 次數
YGCT 從應用程序啟動到采樣時 年輕代 中 gc 所用時間
FGC 從應用程序啟動到采樣時 老年代 中 gc 次數
FGCT 從應用程序啟動到采樣時 老年代 中 gc 所用時間
GCT 從應用程序啟動到采樣時 gc 用的 總時間

2.4. gccapacity

顯示 虛擬機內存 中三代 年輕代young),老年代old),元空間metaspace)對象的使用和占用大小。

1
2
3
$ jstat -gccapacity 8615
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC
87040.0 1397760.0 372736.0 20480.0 10752.0 262144.0 175104.0 2796544.0 165376.0 165376.0 0.0 1079296.0 35456.0 0.0 1048576.0 4992.0 5 2

參數列表及含義如下:

參數 參數含義
NGCMN 年輕代的 初始化(最小)容量
NGCMX 年輕代的 最大容量
NGC 年輕代 當前的容量
S0C 年輕代中 第一個 survivor 區的容量
S1C 年輕代中 第二個 survivor 區的容量
EC 年輕代中 Eden伊甸園)的容量
OGCMN 老年代中 初始化(最小)容量
OGCMX 老年代的 最大容量
OGC 老年代 當前新生成 的容量
OC 老年代的容量大小
MCMN 元空間 的 初始化容量
MCMX 元空間 的 最大容量
MC 元空間 當前 新生成 的容量
CCSMN 最小 壓縮類空間大小
CCSMX 最大 壓縮類空間大小
CCSC 當前 壓縮類空間大小
YGC 從應用程序啟動到采樣時 年輕代 中的 gc 次數
FGC 從應用程序啟動到采樣時 老年代 中的 gc 次數

2.5. gcmetacapacity

顯示 元空間metaspace)中 對象 的信息及其占用量。

1
2
3
$ jstat -gcmetacapacity 8615
MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC FGCT GCT
0.0 1079296.0 35456.0 0.0 1048576.0 4992.0 5 2 0.075 0.131

參數列表及含義如下:

參數 參數含義
MCMN 最小 元數據空間容量
MCMX 最大 元數據空間容量
MC 當前 元數據空間容量
CCSMN 最小壓縮 類空間容量
CCSMX 最大壓縮 類空間容量
CCSC 當前 壓縮類空間容量
YGC 從應用程序啟動到采樣時 年輕代 中 gc 次數
FGC 從應用程序啟動到采樣時 老年代 中 gc 次數
FGCT 從應用程序啟動到采樣時 老年代 gc 所用時間
GCT 從應用程序啟動到采樣時 gc 用的 總時間

2.6. gcnew

顯示 年輕代對象 的相關信息,包括兩個 survivor 區和 一個 Eden 區。

1
2
3
$ jstat -gcnew 8615
S0C S1C S0U S1U TTv MTT DSS EC EU YGC YGCT
20480.0 10752.0 0.0 0.0 6 15 20480.0 262144.0 131406.0 5 0.056

參數列表及含義如下:

參數 參數含義
S0C 年輕代中第一個 survivor 的容量
S1C 年輕代中第二個 survivor 的容量
S0U 年輕代中第一個 survivor 目前已使用空間
S1U 年輕代中第二個 survivor 目前已使用空間
TT 持有次數限制
MTT 最大持有次數限制
DSS 期望的 幸存區 大小
EC 年輕代中 Eden 的容量
EU 年輕代中 Eden 目前已使用空間
YGC 從應用程序啟動到采樣時 年輕代 中 gc 次數
YGCT 從應用程序啟動到采樣時 年輕代 中 gc 所用時間

2.7. gcnewcapacity

查看 年輕代 對象的信息及其占用量。

1
2
3
$ jstat -gcnewcapacity 8615
NGCMN NGCMX NGC S0CMX S0C S1CMX S1C ECMX EC YGC FGC
87040.0 1397760.0 372736.0 465920.0 20480.0 465920.0 10752.0 1396736.0 262144.0 5 2

參數列表及含義如下:

參數 參數含義
NGCMN 年輕代中初始化(最小)的大小
NGCMX 年輕代的最大容量
NGC 年輕代中當前的容量
S0CMX 年輕代中第一個 survivor 的最大容量
S0C 年輕代中第一個 survivor的容量
S1CMX 年輕代中第二個 survivor 的最大容量
S1C 年輕代中第二個 survivor 的容量
ECMX 年輕代中 Eden 的最大容量
EC 年輕代中 Eden 的容量
YGC 從應用程序啟動到采樣時 年輕代 中 gc 次數
FGC 從應用程序啟動到采樣時 老年代 中 gc 次數

2.8. gcold

顯示 老年代對象 的相關信息。

1
2
3
$ jstat -gcold 8615
MC MU CCSC CCSU OC OU YGC FGC FGCT GCT
35456.0 33931.0 4992.0 4582.0 165376.0 24093.7 5 2 0.075 0.131

參數列表及含義如下:

參數 參數含義
MC 元空間metaspace)的容量
MU 元空間metaspace)目前已使用空間
CCSC 壓縮類空間大小
CCSU 壓縮類空間 使用 大小
OC 老年代 的容量
OU 老年代 目前已使用空間
YGC 從應用程序啟動到采樣時 年輕代 中 gc 次數
FGC 從應用程序啟動到采樣時 老年代 中 gc 次數
FGCT 從應用程序啟動到采樣時 老年代 gc 所用時間
GCT 從應用程序啟動到采樣時 gc 用的 總時間

2.9. gcoldcapacity

查看 老年代 對象的信息及其占用量。

1
2
3
$ jstat -gcoldcapacity 8615
OGCMN OGCMX OGC OC YGC FGC FGCT GCT
175104.0 2796544.0 165376.0 165376.0 5 2 0.075 0.131

參數列表及含義如下:

參數 參數含義
OGCMN 老年代 中初始化(最小)的大小
OGCMX 老年代 的最大容量
OGC 老年代 當前新生成的容量
OC 老年代 的容量
YGC 從應用程序啟動到采樣時 年輕代 中 gc 的次數
FGC 從應用程序啟動到采樣時 老年代 中 gc 的次數
FGCT 從應用程序啟動到采樣時 老年代 中 gc 所用時間
GCT 從應用程序啟動到采樣時 gc 用的 總時間

2.10. gcutil

顯示 垃圾回收gc)過程中的信息,包括各個 內存的使用占比,垃圾回收 時間 和回收 次數

1
2
3
$ jstat -gcutil 8615
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 0.00 50.13 14.57 95.70 91.79 5 0.056 2 0.075 0.131

參數列表及含義如下:

參數 參數含義
S0 年輕代中 第一個 survivor 區 已使用 的占當前容量百分比
S1 年輕代中 第二個 survivor 區 已使用 的占當前容量百分比
E 年輕代中 Eden 區 已使用 的占當前容量百分比
O 老年代 中 已使用 的占當前容量百分比
M 元空間metaspace)中 已使用 的占當前容量百分比
YGC 從應用程序啟動到采樣時 年輕代 中 gc 次數
YGCT 從應用程序啟動到采樣時 年輕代 中 gc 所用時間
FGC 從應用程序啟動到采樣時 老年代 gc 次數
FGCT 從應用程序啟動到采樣時 老年代 gc 所用時間
GCT 從應用程序啟動到采樣時 gc 用的 總時間

3. jmap堆內存統計工具

jmap (JVM Memory Map) 命令用來查看 堆內存 使用狀況,一般結合 jhat 使用,用於生成 heap dump 文件。jmap 不僅能生成 dump 文件,還可以查詢 finalize 執行隊列Java  和 元空間 metaspace 的詳細信息,如當前 使用率、當前使用的是哪種 收集器 等等。

如果不使用這個命令,還可以使用 -XX:+HeapDumpOnOutOfMemoryError 參數來讓虛擬機出現 OOM 的時候,自動生成 dump 文件。

命令格式如下:

1
2
3
4
5
6
7
Usage:
jmap [option] <pid>
(to connect to running process)
jmap [option] <executable <core>
(to connect to a core file)
jmap [option] [server_id@]<remote server IP or hostname>
(to connect to remote debug server)

參數含義如下:

  • pid:本地 jvm 服務的進程 ID
  • executable core:打印 堆棧跟蹤 的核心文件;
  • remote server IP/hostname:遠程 debug 服務的 主機名 或 IP 地址;
  • server id:遠程 debug 服務的 進程 ID

參數選項說明如下:

參數 參數含義
heap 顯示  中的摘要信息
histo 顯示  中對象的統計信息
histo[:live] 只顯示  中 存活對象 的統計信息
clstats 顯示 類加載 的統計信息
finalizerinfo 顯示在 F-Queue 隊列 等待 Finalizer 線程執行 finalizer 方法的對象
dump 導出內存轉儲快照

注意:dump 內存快照分析基本上包含了 histoclstatsfinalizerinfo 等功能。

3.1. heap

顯示  中的摘要信息。包括 堆內存 的使用情況,正在使用的 GC 算法堆配置參數 和 各代中堆內存 使用情況。可以用此來判斷內存目前的 使用情況 以及 垃圾回收 情況。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
$ jmap -heap 11368
Attaching to process ID 11368, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.101-b13

using thread-local object allocation.
Parallel GC with 2 thread(s)

Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 2684354560 (2560.0MB)
NewSize = 1073741824 (1024.0MB)
MaxNewSize = 1073741824 (1024.0MB)
OldSize = 1610612736 (1536.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 = 852492288 (813.0MB)
used = 420427144 (400.95056915283203MB)
free = 432065144 (412.04943084716797MB)
49.31741317993014% used
From Space:
capacity = 113770496 (108.5MB)
used = 2299712 (2.19317626953125MB)
free = 111470784 (106.30682373046875MB)
2.021360617079493% used
To Space:
capacity = 107479040 (102.5MB)
used = 0 (0.0MB)
free = 107479040 (102.5MB)
0.0% used
PS Old Generation
capacity = 1610612736 (1536.0MB)
used = 50883368 (48.526161193847656MB)
free = 1559729368 (1487.4738388061523MB)
3.1592552860577903% used

27595 interned Strings occupying 3138384 bytes.

這里主要對 heap configuration 的參數列表說明一下:

參數 對應啟動參數 參數含義
MinHeapFreeRatio -XX:MinHeapFreeRatio JVM堆最小空閑比率(default 40)
MaxHeapFreeRatio -XX:MaxHeapFreeRatio JVM堆最大空閑比率(default 70)
MaxHeapSize XX:Xmx JVM堆的最大大小
NewSize -XX:NewSize JVM堆新生代的默認(初始化)大小
MaxNewSize -XX:MaxNewSize JVM堆新生代的最大大小
OldSize -XX:OldSize JVM堆老年代的默認(初始化)大小
NewRatio -XX:NewRatio JVM堆新生代和老年代的大小比例
SurvivorRatio -XX:SurvivorRatio JVM堆年輕代中Eden區與Survivor區的大小比值
MetaspaceSize -XX:MetaspaceSize JVM元空間(metaspace)初始化大小
MaxMetaspaceSize -XX:MaxMetaspaceSize JVM元空間(metaspace)最大大小
CompressedClass SpaceSize -XX:CompressedClass SpaceSize JVM類指針壓縮空間大小, 默認為1G
G1HeapRegionSize -XX:G1HeapRegionSize 使用G1垃圾回收器時單個Region的大小,取值為1M至32M

3.2. histo

打印堆的 對象統計,包括 對象實例數內存大小 等等。因為在 histo:live 前會進行 full gc,如果帶上 live 則只統計 活對象。不加 live 的堆大小要大於加 live 堆的大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ jmap -histo:live 12498
num #instances #bytes class name
----------------------------------------------
1: 50358 7890344 [C
2: 22887 2014056 java.lang.reflect.Method
3: 3151 1485512 [B
4: 49267 1182408 java.lang.String
5: 7836 871384 java.lang.Class
6: 24149 772768 java.util.concurrent.ConcurrentHashMap$Node
7: 20785 482256 [Ljava.lang.Class;
8: 8357 435248 [Ljava.lang.Object;
9: 10035 401400 java.util.LinkedHashMap$Entry
10: 4803 369488 [Ljava.util.HashMap$Node;
11: 10763 344416 java.util.HashMap$Node
12: 5205 291480 java.util.LinkedHashMap
13: 3055 219960 java.lang.reflect.Field
14: 120 193408 [Ljava.util.concurrent.ConcurrentHashMap$Node;
15: 11224 179584 java.lang.Object
16: 1988 146152 [Ljava.lang.reflect.Method;
17: 3036 145728 org.aspectj.weaver.reflect.ShadowMatchImpl
18: 1771 141680 java.lang.reflect.Constructor
19: 4903 117672 org.springframework.core.MethodClassKey
20: 3263 104416 java.lang.ref.WeakReference
21: 2507 100280 java.lang.ref.SoftReference
22: 2523 97600 [I
23: 3036 97152 org.aspectj.weaver.patterns.ExposedState
24: 2072 95280 [Ljava.lang.String;
25: 954 91584 org.springframework.beans.GenericTypeAwarePropertyDescriptor
26: 1633 91448 java.lang.Class$ReflectionData
27: 3142 90520 [Z
28: 1671 80208 java.util.HashMap
29: 3244 77856 java.util.ArrayList
30: 3037 72880 [Lorg.aspectj.weaver.ast.Var;
31: 1809 72360 java.util.WeakHashMap$Entry
32: 1967 62944 java.util.LinkedList

其中,class name 是 對象類型,對象 縮寫類型 與 真實類型 的對應說明如下:

對象縮寫類型 對象真實類型
B byte
C char
D double
F float
I int
J long
Z boolean
[ 數組,如[I表示int[]
[L+類名 其他對象

3.3. dump

dump 用於導出內存轉儲快照。常用的方式是通過 jmap 把進程 內存使用情況 dump 到文件中,再用 jhat 分析查看。jmap 進行 dump 的命令格式如下:

1
jmap -dump:format=b,file=dumpFileName

參數含義如下:

參數 參數含義
dump 堆到文件
format 指定輸出格式
live 指明是活着的對象
file 指定文件名
  • 通過 jmap 導出 內存快照,文件命名為 dump.dat
1
2
3
jmap -dump:format=b,file=dump.dat 12498
Dumping heap to /Users/XXX/dump.dat ...
Heap dump file created

導出的 dump 文件可以通過 MATVisualVM 和 jhat 等工具查看分析,后面會詳細介紹。

4. jhat堆快照分析工具

jhatJVM Heap Analysis Tool)命令通常與 jmap 搭配使用,用來分析 jmap 生成的 dumpjhat 內置了一個微型的 HTTP/HTML 服務器,生成 dump 的分析結果后,可以在瀏覽器中查看。

注意:一般不會直接在 服務器 上 進行分析,因為使用 jhat 是一個 耗時 並且 耗費硬件資源 的過程,一般的做法是,把 服務器 生成的 dump 文件復制到 本地 或 其他機器 上進行分析。

命令格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Usage:  jhat [-stack <bool>] [-refs <bool>] [-port <port>] [-baseline <file>] [-debug <int>] [-version] [-h|-help] <file>

-J<flag> Pass <flag> directly to the runtime system. For
example, -J-mx512m to use a maximum heap size of 512MB
-stack false: Turn off tracking object allocation call stack.
-refs false: Turn off tracking of references to objects
-port <port>: Set the port for the HTTP server. Defaults to 7000
-exclude <file>: Specify a file that lists data members that should
be excluded from the reachableFrom query.
-baseline <file>: Specify a baseline object dump. Objects in
both heap dumps with the same ID and same class will
be marked as not being "new".
-debug <int>: Set debug level.
0: No debug output
1: Debug hprof file parsing
2: Debug hprof file parsing, no server
-version Report version number
-h|-help Print this help and exit
<file> The file to read

參數含義如下:

參數 參數值默認值 參數含義
stack true 關閉 對象分配調用棧跟蹤。如果分配位置信息在堆轉儲中不可用。則必須將此標志設置為false。
refs true 關閉 對象引用跟蹤。默認情況下,返回的指針是指向其他特定對象的對象。如 反向鏈接 或 輸入引用,會統計/計算堆中的所有對象
port 7000 設置jhat HTTP server的端口號
exclude 指定對象查詢時需要排除的數據成員列表文件
baseline 指定一個 基准堆轉儲。在兩個heap dumps中有相同object ID的對象時,會被標記為不是新的,其他對象被標記為新的。在比較兩個不同的堆轉儲時很有用
debug 0 設置debug級別,0表示不輸出調試信息。值越大則表示輸出更詳細的debug信息
version 啟動后只顯示版本信息就退出
J jhat命令實際上會啟動一個JVM來執行,通過-J可以在啟動JVM時傳入一些 啟動參數。例如, -J-Xmx512m則指定運行jhat 的Java虛擬機使用的最大堆內存為512MB。
  • 前面提到,通過 jmap dump 出來的文件可以用 MATVisualVM 等工具查看,這里我們用 jhat 查看:
1
2
3
4
5
6
7
8
9
10
$ jhat -port 7000 dump.dat
Reading from dump.dat...
Dump file created Sun Aug 12 12:15:02 CST 2018
Snapshot read, resolving...
Resolving 1788693 objects...
Chasing references, expect 357 dots.....................................................................................................................................................................................................................................................................................................................................................................
Eliminating duplicate references.....................................................................................................................................................................................................................................................................................................................................................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
  • 打開瀏覽器,輸入 http://localhost:7000,查看 jhat 的分析報表頁面:

  • 可以按照 包名稱 查看項目模塊中的具體 對象示例

除此之外,報表分析的最后一頁,還提供了一些擴展查詢:

  • 顯示所有的 Root 集合;
  • 顯示所有 class 的當前 對象實例數量(包含 JVM 平台相關類);
  • 顯示所有 class 的當前 對象實例數量(除去 JVM 平台相關類);
  • 顯示 堆內存 中實例對象的 統計直方圖(和直接使用 jmap 沒有區別);
  • 顯示 finalizer 虛擬機 二次回收 的信息摘要;
  • 執行 jhat 提供的 對象查詢語言OQL)獲取指定對象的實例信息。

注意:jhat 支持根據某些條件來 過濾 或 查詢 堆的對象。可以在 jhat 的 html 頁面中執行 OQL 語句,來查詢符合條件的對象。OQL `具體的語法可以直接訪問 http://localhost:7000/oqlhelp。

在具體排查時,需要結合代碼,觀察是否 大量應該被回收 的對象 一直被引用,或者是否有 占用內存特別大 的對象 無法被回收

5. jstack堆棧跟蹤工具

jstack 用於生成 java 虛擬機當前時刻的 線程快照線程快照 是當前 java 虛擬機內 每一條線程 正在執行的 方法堆棧 的 集合。生成線程快照的主要目的是定位線程出現 長時間停頓 的原因,如 線程間死鎖死循環請求外部資源 導致的 長時間等待 等等。

線程出現 停頓 的時候,通過 jstack 來查看 各個線程 的 調用堆棧,就可以知道沒有響應的線程到底在后台做什么事情,或者等待什么資源。如果 java 程序 崩潰 生成 core 文件jstack 工具可以通過 core 文件獲取 java stack 和 native stack 的信息,從而定位程序崩潰的原因。

命令格式如下:

1
2
3
4
5
6
7
8
9
Usage:
jstack [-l] <pid>
(to connect to running process)
jstack -F [-m] [-l] <pid>
(to connect to a hung process)
jstack [-m] [-l] <executable> <core>
(to connect to a core file)
jstack [-m] [-l] [server_id@]<remote server IP or hostname>
(to connect to a remote debug server)

參數含義如下:

  • pid:本地 jvm 服務的進程 ID
  • executable core:打印 堆棧跟蹤 的核心文件;
  • remote server IP/hostname:遠程 debug 服務的 主機名 或 IP 地址;
  • server id:遠程 debug 服務的 進程 ID

參數選項說明如下:

參數 參數含義
F 當正常輸出請求 不被響應 時,強制輸出 線程堆棧
l 除堆棧外,顯示關於 鎖的附加信息
m 如果調用到 本地方法 的話,可以顯示 C/C++ 的堆棧

注意:在實際運行中,往往一次 dump 的信息,還不足以確認問題。建議產生三次 dump 信息,如果每次 dump 都指向同一個問題,才能確定問題的典型性。

5.1. 系統線程狀態

在 dump 文件里,值得關注的 線程狀態 有:

  1. 死鎖:Deadlock(重點關注)
  2. 執行中:Runnable
  3. 等待資源:Waiting on condition(重點關注)
  4. 等待獲取監視器:Waiting on monitor entry(重點關注)
  5. 暫停:Suspended
  6. 對象等待中:Object.wait() 或 TIMED_WAITING
  7. 阻塞:Blocked(重點關注)
  8. 停止:Parked

具體的含義如下所示:

(a). Deadlock

死鎖線程,一般指多個線程調用期間發生 資源的相互占用,導致一直等待無法釋放的情況。

(b). Runnable

一般指該線程正在 執行狀態 中,該線程占用了 資源,正在 處理某個請求。有可能正在傳遞 SQL 到數據庫執行,有可能在對某個文件操作,有可能進行數據類型等轉換。

(c). Waiting on condition

該狀態在線程等待 某個條件 的發生。具體是什么原因,可以結合 stacktrace來分析。線程處於這種 等待狀態,一旦有數據准備好讀之后,線程會重新激活,讀取並處理數據。

線程正處於等待資源或等待某個條件的發生,具體的原因需要結合下面堆棧信息進行分析。

  • 如果 堆棧信息 明確是 應用代碼,則證明該線程正在 等待資源。一般是大量 讀取某種資源 且該資源采用了 資源鎖 的情況下,線程進入 等待狀態

  • 如果發現有 大量的線程 都正處於這種狀態,並且堆棧信息中得知正在 等待網絡讀寫,這是因為 網絡阻塞 導致 線程無法執行,很有可能是一個 網絡瓶頸 的征兆:

    • 網絡非常 繁忙,幾乎消耗了所有的帶寬,仍然有大量數據等待網絡讀寫;
    • 網絡可能是 空閑的,但由於 路由 或 防火牆 等原因,導致包無法正常到達。
  • 還有一種常見的情況是該線程在 sleep,等待 sleep 的時間到了,將被喚醒。

(d). Locked

線程阻塞,是指當前線程執行過程中,所需要的資源 長時間等待 卻 一直未能獲取到,被容器的線程管理器標識為 阻塞狀態,可以理解為 等待資源超時 的線程。

(e). Waiting for monitor entry 和 in Object.wait()

Monitor 是 Java 中實現線程之間的 互斥與協作 的主要手段,它可以看成是 對象 或者 Class 的 。每一個對象都有一個 monitor

5.1. 死鎖示例

下面給出一個 死鎖 的案例,在 IntLock 中定義了兩個靜態的 可重入鎖 實例,在主方法中聲明了 兩個線程 對 兩把鎖 進行資源競爭。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class DeadLockRunner {
public static void main(String[] args) {
IntLock r1 = new IntLock(1);
IntLock r2 = new IntLock(2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}

public static class IntLock implements Runnable {
private static ReentrantLock lock1 = new ReentrantLock();
private static ReentrantLock lock2 = new ReentrantLock();
private int lock;

public IntLock(int lock) {
this.lock = lock;
}

@Override
public void run() {
try {
if (lock == 1) {
lock1.lock();

try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}

lock2.lock();
} else {
lock2.lock();

try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}

lock1.lock();
}
} finally {
if (lock1.isHeldByCurrentThread()) {
lock1.unlock();
}
if (lock2.isHeldByCurrentThread()) {
lock2.unlock();
}
}
}
}
}

5.2. dump日志分析

啟動 DeadLockRunner 的 main() 方法,使用 jps 查看阻塞的 jvm 進程的 id,然后使用 jstack 查看 線程堆棧信息,可以發現兩個線程相互 競爭資源,出現死鎖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$ jstack -l 15584
2018-08-12 20:35:40
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.172-b11 mixed mode):

// 省略...

Found one Java-level deadlock:
=============================
"Thread-1":
waiting for ownable synchronizer 0x000000076ad61180, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "Thread-0"
"Thread-0":
waiting for ownable synchronizer 0x000000076ad611b0, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000076ad61180> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at io.ostenant.deadlock.DeadLockRunner$IntLock.run(DeadLockRunner.java:47)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000076ad611b0> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at io.ostenant.deadlock.DeadLockRunner$IntLock.run(DeadLockRunner.java:37)
at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

參考

周志明,深入理解Java虛擬機:JVM高級特性與最佳實踐,機械工業出版社


歡迎關注技術公眾號:零壹技術棧

零壹技術棧零壹技術棧

本帳號將持續分享后端技術干貨,包括虛擬機基礎,多線程編程,高性能框架,異步、緩存和消息中間件,分布式和微服務,架構學習和進階等學習資料和文章。

 


免責聲明!

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



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