前言
通過上一篇的 JVM
垃圾回收知識,我們瞭解了 JVM
具體的 垃圾回收演算法 和幾種 垃圾回收器。理論是指導實踐的工具,有了理論指導,定位問題的時候,知識和經驗是關鍵基礎,資料可以為我們提供依據。
線上上我們經常會遇見如下幾個問題:
- 記憶體洩露;
- 某個程式突然
CPU
飆升; - 執行緒死鎖;
- 響應變慢。
如果遇到了以上這種問題,在 線下環境 可以有各種 視覺化的本地工具 支援檢視。但是一旦到 線上環境,就沒有這麼多的 本地除錯工具 支援,我們該如何基於 監控工具 來進行定位問題?
我們一般會基於 資料收集 來定位問題,而資料的收集離不開 監控工具 的處理,比如:執行日誌、異常堆疊、GC
日誌、執行緒快照、堆記憶體快照 等。為了解決以上問題,我們常用的 JVM
效能調優監控工具 大致有:jps
、jstat
、jstack
、jmap
、jhat
、hprof
、jinfo
。
正文
如果想要檢視 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
名稱等資訊。
命令格式如下:
usage: jps [-help]
jps [-q] [-mlvV] [<hostid>]
Definitions:
<hostid>: <hostname>[:<port>]
複製程式碼
引數含義如下:
- -q: 不輸出 類名稱、
Jar
名稱 和傳入main
方法的 引數; - -l: 輸出
main
類或Jar
的 全限定名稱; - -m: 輸出傳入
main
方法的 引數; - -v: 輸出傳入
JVM
的引數。
2. jinfo配置資訊檢視工具
jinfo
(JVM Configuration info
)這個命令作用是實時檢視和調整 虛擬機器執行引數。之前的 jps -v
命令只能檢視到顯示 指定的引數,如果想要檢視 未顯示 的引數的值就要使用 jinfo
命令。
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
程式的 擴充套件引數。
$ 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
程式的所有 引數資訊。
$ 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
程式的 環境變數資訊。
$ 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
監控功能:
- 類的載入 及 解除安裝 的情況;
- 檢視 新生代、老生代 及 元空間(
MetaSpace
)的 容量 及使用情況; - 檢視 新生代、老生代 及 元空間(
MetaSpace
)的 垃圾回收情況,包括垃圾回收的 次數,垃圾回收所佔用的 時間; - 檢視 新生代 中
Eden
區及Survior
區中 容量 及 分配情況 等。
命令格式如下:
Usage: jstat -help|-options
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
複製程式碼
引數含義如下:
- option: 引數選項。
- -t: 可以在列印的列加上
timestamp
列,用於顯示系統執行的時間。 - -h: 可以在 週期性資料 的時候,可以在指定輸出多少行以後輸出一次 表頭。
- -t: 可以在列印的列加上
- 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
顯示和監視 類裝載、解除安裝數量、總空間 以及 耗費的時間。
$ 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
)的 次數 和 耗時 等資訊。
$ 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
的 次數 及 時間。
$ 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
:
$ 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
)物件的使用和佔用大小。
$ 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
)中 物件 的資訊及其佔用量。
$ 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
區。
$ 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
檢視 年輕代 物件的資訊及其佔用量。
$ 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
顯示 老年代物件 的相關資訊。
$ 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
檢視 老年代 物件的資訊及其佔用量。
$ 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
)過程中的資訊,包括各個 記憶體的使用佔比,垃圾回收 時間 和回收 次數。
$ 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
檔案。
命令格式如下:
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
記憶體快照分析基本上包含了histo
、clstats
、finalizerinfo
等功能。
3.1. heap
顯示 堆 中的摘要資訊。包括 堆記憶體 的使用情況,正在使用的 GC
演算法、堆配置引數 和 各代中堆記憶體 使用情況。可以用此來判斷記憶體目前的 使用情況 以及 垃圾回收 情況。
$ 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
堆的大小。
$ 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
的命令格式如下:
jmap -dump:format=b,file=dumpFileName
複製程式碼
引數含義如下:
引數 | 引數含義 |
---|---|
dump | 堆到檔案 |
format | 指定輸出格式 |
live | 指明是活著的物件 |
file | 指定檔名 |
- 通過
jmap
匯出 記憶體快照,檔案命名為dump.dat
:
jmap -dump:format=b,file=dump.dat 12498
Dumping heap to /Users/XXX/dump.dat ...
Heap dump file created
複製程式碼
匯出的 dump
檔案可以通過 MAT
、VisualVM
和 jhat
等工具檢視分析,後面會詳細介紹。
4. jhat堆快照分析工具
jhat
(JVM Heap Analysis Tool
)命令通常與 jmap
搭配使用,用來分析 jmap
生成的 dump
。jhat
內建了一個微型的 HTTP/HTML
伺服器,生成 dump
的分析結果後,可以在瀏覽器中檢視。
注意:一般不會直接在 伺服器 上 進行分析,因為使用
jhat
是一個 耗時 並且 耗費硬體資源 的過程,一般的做法是,把 伺服器 生成的dump
檔案複製到 本地 或 其他機器 上進行分析。
命令格式如下:
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
出來的檔案可以用MAT
、VisualVM
等工具檢視,這裡我們用jhat
檢視:
$ 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
的資訊,從而定位程式崩潰的原因。
命令格式如下:
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
檔案裡,值得關注的 執行緒狀態 有:
- 死鎖:Deadlock(重點關注)
- 執行中:Runnable
- 等待資源:Waiting on condition(重點關注)
- 等待獲取監視器:Waiting on monitor entry(重點關注)
- 暫停:Suspended
- 物件等待中:Object.wait() 或 TIMED_WAITING
- 阻塞:Blocked(重點關注)
- 停止: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
中定義了兩個靜態的 可重入鎖 例項,在主方法中宣告瞭 兩個執行緒 對 兩把鎖 進行資源競爭。
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
檢視 執行緒堆疊資訊,可以發現兩個執行緒相互 競爭資源,出現死鎖。
$ 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高階特性與最佳實踐,機械工業出版社
歡迎關注技術公眾號:零壹技術棧
本帳號將持續分享後端技術乾貨,包括虛擬機器基礎,多執行緒程式設計,高效能框架,非同步、快取和訊息中介軟體,分散式和微服務,架構學習和進階等學習資料和文章。