JDK 自带工具

本地环境安装过完整 JDK 包之后,会在 $JAVA_HOME/bin 目录下发现很多工具,包括编译命令 javac、执行命令 java 等。本节我们梳理一下其中用于 JVM 运维的工具。

jps

jps (Java Virtual Machine Process Status Tool)

jps 是 JVM 进程查找工具,类似于 linux 的 ps 命令。我们使用这个命令主要是为了找到当前正在运行的 JVM 及其进程 ID。。语法格式如下:

1
jps [options] [hostid]

如果不指定 hostid 就默认为当前主机或服务器。

命令行参数选项说明如下:

1
2
3
4
5
6
7
8
-q 不输出类名、Jar名和传入main方法的参数
# 10517
-m 输出传入main方法的参数
# 10517 TestCase01GCTest testParam
-l 输出main类或Jar的全限名
# 10517 czhao.study.jvm.TestCase01GCTest
-v 输出传入JVM的参数
# 10517 TestCase01GCTest -Xms20M -Xmx20M -XX:+UseG1GC -XX:MaxGCPauseMillis=200

jstat

https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html

jstat 是 JDK 自带的一个轻量级小工具。全称“Java Virtual Machine statistics monitoring tool”,主要利用 JVM 内建的指令对 Java 应用程序的资源和性能进行实时的命令行的监控,包括了对 Heap size 和垃圾回收状况的监控。

下面展示常用命令 jstat -<option> <pid> [<interval> [<count>]]

  • <pid>: Java 进程的进程 ID。
  • <interval>: 采样的时间间隔(以毫秒为单位)。如果不提供此参数,默认为 1000 毫秒(1 秒)。
  • <count>: 采样的次数。如果不提供此参数,默认将一直进行采样,直到命令被手动终止。
option 说明
-class 显示加载 class 的数量,及所占空间等信息
-compiler 显示 VM 实时编译的数量等信息
-gc 可以显示 gc 的信息,查看 gc 的次数,及时间
-gccapacity 查看内存中堆的分代内存大小
gccause 显示有关垃圾收集统计信息的摘要
-gcnew 年轻代对象的信息
-gcnewcapacity 年轻代空间大小统计
-gcold 年老代空间大小统计
-gcoldcapacity 年老代容量空间大小统计
-gcmetacapacity 元数据空间统计
-gcutil 垃圾收集统计信息摘要
-printcompilation Java HotSpot 虚拟机 编译方法统计

–class

1
2
3
c: \Users\dev>jstat-class13380 Loaded Bytes Unloaded Bytes Time 18051 32345.3 0 0
112.14 Loaded 装载的类的数量 Bytes 装载的字节数 Unloaded 卸载的类的数量 Bytes 卸载的类字节
Time(毫秒) 装载和卸载的使用时间;

-compiler

1
2
3
4
5
6
7
8
9
10
C:\Users\dev>jstat -compiler 13380
Compiled Failed Invalid Time FailedType FailedMethod
20924 12 0 152.82 1 org/apache/catalina/startup/HostConfig deployWARs

Compiled 编译任务执行数量
Failed 编译任务执行错误的数量
Invalid 编译任务执行失效的数量
Time 编译任务执行的时间
FailedType 编译任务最后一个执行的类型
FailedMethod 最后一个编译失败任务所在的类及方法

-gc

1
2
3
4
例子如下
jstat -gc 86517
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
66560.0 66560.0 0.0 7215.7 532480.0 89819.8 1431552.0 1291011.4 108752.0 101631.2 15004.0 11994.8 10491 163.755 0 0.000 163.755
列名 描述
S0C 幸存区 Survior1 S0 空间容量 (kb)
S1C 幸存区 Survior S1 空间容量(kb)
S0U 幸存区 Survior1 S0 使用空间(kb)
S1U 幸存区 Survior S1 使用空间(kb)
EC Eden 区空间容量(kb)
EU Eden 区使用空间(kb)
OC 老年代 Old 空间容量(kb)
OU 老年代 Old(kb)
MC 元空间 MetaSpace 空间容量(kb)
MU 元空间 MetaSpace 使用空间(kb)
CCSC 压缩类空间容量(kb)
CCSU 压缩类使用空间(kb)
YGC 年轻代 gc 回收数量
YGCT 年轻代 gc 收集时间
FGC full gc 执行数量
FGCT full gc 执行总时间
GCT 总垃圾回收时间

-gccapacity

1
2
3
4
查看内存中堆的分代内存大小
jstat -gccapacity 86517
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC MCMN MCMX MC CCSMX CCSC YGC FGC
0.0 665600.0 665600.0 66560.0 66560.0 532480.0 0.0 1431552.0 1431552.0 1431552.0 0.0 1138688.0 104144.0 0.0 1048576.0 14492.0 350 0
列名 描述
NGCMN Young 年轻代最小的新一代容量(kB)
NGCMX Young 年轻代最大新一代容量(kB)
NGC Young 年轻代 当前容量(kB)
S0C 幸存区 Survior S0 空间容量 (kB)
S1C 幸存区 Survior S1 空间容量 (kB)
EC Eden 区空间容量(kB)
OGCMN Old 老年代最小的新一代容量(kB)
OGCMX Old 老年代最大新一代容量(kB)
OGC 当前 Old 老年代容量(kB)
OC 当前旧的 Old 老年代容量(kB)
MCMN 最小 Metaspace 元空间容量(kB)
MCMX 最大 Metaspace 元空间容量(kB)
MC Metaspace 元空间容量(kB)
CCSMN 压缩类最小空间(kB)
CCSMX 压缩类最大空间(kB)
CCSC 压缩类的空间(kB)
YGC 年轻代 GC 事件的数量
FGC Full GC 事件的数量

-gccause

1
2
3
4
显示有关垃圾收集统计信息的摘要
jstat -gccause 86517
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT LGCC GCC
0.00 7.93 42.77 86.85 93.51 79.91 425 9.173 0 0.000 9.173 CMS Initial Mark No GC
列名 描述
S0 幸存区 Survior S0 利用率占空间当前容量的百分比
S1 幸存区 Survior S1 利用率占空间当前容量的百分比
E Eden 区利用率占空间当前容量的百分比
O Old 老年代利用率占空间当前容量的百分比
M Metaspace 元空间利用率占空间当前容量的百分比
CCS 以百分比形式压缩的类空间利用率
YGC 年轻代 GC 事件的数量
YGCT 年轻代垃圾回收时间
FGCT 老年代 GC 时间
GCT 总垃圾回收时间
LGCC 上次垃圾回收的原因
GCC 当前垃圾回收的原因

-gcnew

1
2
3
4
年轻代对象的信息
jstat -gcnew 86517
S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT
66560.0 66560.0 0.0 5509.0 10 10 33280.0 532480.0 173025.7 465 9.724
列名 描述
S0C 幸存区 Survior S0 空间容量 (kb)
S1C 幸存区 Survior S1 空间容量(kb)
S0U 幸存区 Survior1 S0 使用空间(kb)
S1U 幸存区 Survior S1 使用空间(kb)
TT 年轻代晋升到年老代的阈值
MTT 年轻代晋升到年老代最大阈值
DSS 所需的 Survior1 幸存者大小
EC Eden 区空间容量(kb)
EU Eden 区使用空间(kb)
YGC 年轻代 gc 回收数量
YGCT 年轻代 gc 收集时间

-gcnewcapacity

1
2
3
4
年轻代空间大小统计
jstat -gcnewcapacity 86517
NGCMN NGCMX NGC S0CMX S0C S1CMX S1C ECMX EC YGC FGC
0.0 665600.0 665600.0 66560.0 66560.0 66560.0 66560.0 532480.0 532480.0 512 0
列名 描述
NGCMN Young 年轻代最小的新一代容量(kB)
NGCMX Young 年轻代最大新一代容量(kB)
NGC Young 年轻代 当前容量(kB)
S0CMX 幸存区 Survior S0 最大容量(kB)
S0c 存区 Survior S0 空间容量 (kb)
S1CMX 幸存区 Survior S1 最大容量(kB)
S1C 存区 Survior S1 空间容量 (kb)
ECMX Eden 区空间最大容量(kb)
EC Eden 区空间容量(kb)
YGC 年轻代 gc 回收数量
FGC Full gc 执行回收数量

-gcold

1
2
3
4
年老代空间大小统计
jstat -gcold 86517
MC MU CCSC CCSU OC OU YGC FGC FGCT GCT
104656.0 97895.1 14492.0 11590.2 1431552.0 1245031.1 924 0 0.000 16.289
列名 描述
MC Metaspace 元空间容量(kB)
MU 元空间 MetaSpace 使用空间(kb)
CCSC 压缩类的空间(kB)
CCSU 压缩类使用空间(kb)
OC 老年代 Old 空间容量(kb)
OU 老年代 Old(kb)
YGC 年轻代 gc 回收数量
FGC Full gc 执行回收数量
FGCT 老年代 GC 时间
GCT 总垃圾回收时间

-gcoldcapacity

1
2
3
4
年老代容量空间大小统计
jstat -gcoldcapacity 86517
OGCMN OGCMX OGC OC YGC FGC FGCT GCT
0.0 1431552.0 1431552.0 1431552.0 1431552.0 939 0 0.000 16.459
列名 描述
OGCMN Old 年老代最小容量(kb)
OGCMX Old 年老代最大容量(kb)
OGC 当前 Old 老年代容量(kB)
OC 当前旧的 Old 老年代容量(kB)
YGC 年轻代 gc 回收数量
FGC Full gc 执行回收数量
FGCT 老年代 GC 时间
GCT 总垃圾回收时间

-gcmetacapacity

1
2
3
4
元数据空间统计
jstat -gcmetacapacity 86517
MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC FGCT GCT
0.0 1140736.0 104656.0 0.0 1048576.0 14492.0 965 0 0.000 16.805
列名 描述
MCMN 最小 Metaspace 元空间容量(kB)
MCMX 最大 Metaspace 元空间容量(kB)
MC Metaspace 元空间容量(kB)
CCSMN 压缩类最小空间(kB)
CCSMX 压缩类最大空间(kB)
CCSC 压缩类空间容量(kb)
CCSU 压缩类使用空间(kb)
YGC 年轻代 gc 回收数量
FGC Full gc 执行回收数量
FGCT 老年代 GC 时间
GCT 总垃圾回收时间

gcutil

1
2
3
4
垃圾收集统计信息摘要
jstat -gcutil 86517
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 7.40 71.87 86.94 93.55 79.98 979 16.992 0 0.000 16.992
列名 描述
S0 幸存区 Survior S0 利用率占空间当前容量的百分比
S1 幸存区 Survior S1 利用率占空间当前容量的百分比
E Eden 区利用率占空间当前容量的百分比
O Old 老年代利用率占空间当前容量的百分比
M Metaspace 元空间利用率占空间当前容量的百分比
CCS 以百分比形式压缩的类空间利用率
YGC 年轻代 GC 事件的数量
YGCT 年轻代垃圾回收时间
FGCT 老年代 GC 时间
GCT 总垃圾回收时间
1

-printcompilation

1
2
3
4
Java HotSpot 虚拟机 编译方法统计
jstat -printcompilation 86517
Compiled Size Type Method
29939 77 1 org/apache/lucene/codecs/perfield/PerFieldDocValuesFormat$FieldsWriter <init>
列名 描述
Compiled 最近编译的方法执行的编译任务数
Size 最近编译的方法的字节码的字节数
Type 最近编译的方法的编译类型
Method 标识最近编译的方法的类名和方法名

jstack

jstack 用于生成虚拟机当前时刻的线程快照。生成线程快照主要是为了定位长时间停顿的线程,比如线程间死锁、死循环、请求外部资源超时等等。通过 jstack 可以查看到各个线程的调用堆栈信息,就可以知道线程目前运行在哪一句代码,在做什么事情或者等待什么资源。

同样的,jstack 使用时,也要注意用户、版本是否与目标 JVM 一致。

1
2
3
jstack [option] pid
jstack [option] executable core
jstack [option] [server-id@]remote-hostname-or-ip

命令行参数选项说明如下:

1
2
3
-F :强制输出线程堆栈
-l long listings,会打印出额外的锁信息,在发生死锁时可以用jstack -l pid来观察锁持有情况
-m mixed mode,不仅会输出Java堆栈信息,还会输出C/C++堆栈信息(比如Native方法)

jstack 可以定位到线程堆栈,根据堆栈信息我们可以定位到具体代码,所以它在 JVM 性能调优中使用得非常多。

jmap

jmap 是 java 内存映像工具,主要用于查询当前堆和方法区的详细信息,生成堆的快照文件等。一般都是使用 -XX:+HeapDumpOnOutOfMemoryError 参数指定 JVM 在内存溢出异常时自动生成堆的快照文件。之后在服务器发生内存溢出异常时,将对应的快照文件拉取到本地使用工具分析。

常用的 options 参数:

1
2
3
4
5
6
-heap 显示 Java 堆详细信息
-histo 显示堆中对象的统计
-permstat 显示堆永久区的类加载器的统计信息
-permstat 显示堆永久区的类加载器的统计信息
-finalizerinfo 显示在 F-Queue 队列等待 Finalizer 线程执行 finalize 方法的对象
-dump 生成堆转储快照

jmap 的堆转储快照文件有很多工具可以分析,常用的有 MAT,Jprofiler,IBM HeapAnalyzer 等,后续章节会一一介绍。

可以使用 jmap -histo[:live] <pid> 分析当前堆中对象,例如 ./jmap -histo:live 10517

可以使用 jmap -dump:[live,]format=b,file=<file> <pid> 对目标 JVM 进行快照转储,例如:

1
2
3
4
5
6
7
8
只转储存活对象: 使用 hprof 二进制形式, 输出 jvm 的 heap 内容到文件
./jmap -dump:live,format=b,file=/home/work/sources/test/study-jvm/output/jmap/dump001 10517

# 转储所有对象
./jmap -dump:format=b,file=/home/work/sources/test/study-jvm/output/jmap/dump002 10517

# 打印正等候回收的对象的信息
jmap -finalizerinfo pid

jmap(Memory Map)和 jhat(Java Heap Analysis Tool)

jmap 用来查看堆内存使用状况,一般结合 jhat 使用。

可以输出所有内存中对象的工具(如:产生那些对象,及其数量),甚至可以将 VM 中的 heap,以二进制输出成文本。

jhat

jhat 是一个原始简陋的用来分析 jmap 的堆转储快照文件的工具,Java9 之后已经被移出 JDK 的工具包。这里了解一下即可。

我们用 Java8 的 jhat 工具对刚才 jmap 转储的快照文件进行分析:

1
2
3
4
5
6
7
8
9
10
jhat dump001
Reading from dump001...
Dump file created Mon Feb 08 12:37:58 CST 2021
Snapshot read, resolving...
Resolving 28806 objects...
Chasing references, expect 5 dots.....
Eliminating duplicate references.....
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

然后访问 http://localhost:7000/ 就可以看到这个简陋的分析页面。

jinfo

jinfo 用于查看 JVM 参数信息,并可以实时调整少量可以在运行时改变的参数。

使用 jinfo 时要注意两点:

  • 执行 jinfo 命令的用户与目标 JVM 的启动用户应该是同一个用户,避免权限不足。
  • jinfo 命令版本与目标 JVM 的 java 命令版本一致,应该是同一个 JDK 目录下的命令。

查看目标 JVM 的完整 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
65
66
67
68
# 因为之前启动的JVM使用的是 /usr/java/jdk-11.0.7+10/bin/java ,所以这里需要使用对应相同目录下的 jinfo
zhaochun@zhaochun-T480:bin$ pwd
/usr/java/jdk-11.0.7+10/bin

zhaochun@zhaochun-T480:bin$ ./jinfo 10517
Java System Properties:
#Mon Feb 08 11:49:06 CST 2021
sun.desktop=gnome
awt.toolkit=sun.awt.X11.XToolkit
java.specification.version=11
sun.cpu.isalist=
sun.jnu.encoding=UTF-8
java.class.path=.\:/usr/java/jdk1.8.0_131/lib\:/usr/java/jdk1.8.0_131/jre/lib
java.vm.vendor=AdoptOpenJDK
sun.arch.data.model=64
java.vendor.url=https\://adoptopenjdk.net/
user.timezone=Asia/Shanghai
java.vm.specification.version=11
os.name=Linux
sun.java.launcher=SUN_STANDARD
user.country=CN
sun.boot.library.path=/usr/java/jdk-11.0.7+10/lib
sun.java.command=czhao.study.jvm.TestCase01GCTest testParam
jdk.debug=release
sun.cpu.endian=little
user.home=/home/zhaochun
user.language=zh
java.specification.vendor=Oracle Corporation
java.version.date=2020-04-14
java.home=/usr/java/jdk-11.0.7+10
file.separator=/
java.vm.compressedOopsMode=32-bit
line.separator=\n
java.vm.specification.vendor=Oracle Corporation
java.specification.name=Java Platform API Specification
java.awt.graphicsenv=sun.awt.X11GraphicsEnvironment
sun.management.compiler=HotSpot 64-Bit Tiered Compilers
java.runtime.version=11.0.7+10
user.name=zhaochun
path.separator=\:
os.version=4.15.0-20-generic
java.runtime.name=OpenJDK Runtime Environment
file.encoding=UTF-8
java.vm.name=OpenJDK 64-Bit Server VM
java.vendor.version=AdoptOpenJDK
java.vendor.url.bug=https\://github.com/AdoptOpenJDK/openjdk-support/issues
java.io.tmpdir=/tmp
java.version=11.0.7
user.dir=/home/work/sources/test/study-jvm/out/production/study-jvm
os.arch=amd64
java.vm.specification.name=Java Virtual Machine Specification
java.awt.printerjob=sun.print.PSPrinterJob
sun.os.patch.level=unknown
java.library.path=/usr/java/packages/lib\:/usr/lib64\:/lib64\:/lib\:/usr/lib
java.vendor=AdoptOpenJDK
java.vm.info=mixed mode
java.vm.version=11.0.7+10
sun.io.unicode.encoding=UnicodeLittle
java.class.version=55.0

VM Flags:
-XX:CICompilerCount=4 -XX:ConcGCThreads=2 -XX:G1ConcRefinementThreads=8 -XX:G1HeapRegionSize=1048576 -XX:GCDrainStackTargetSize=64 -XX:InitialHeapSize=20971520 -XX:MarkStackSize=4194304 -XX:MaxGCPauseMillis=200 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MinHeapDeltaBytes=1048576 -XX:NewSize=10485760 -XX:NonNMethodCodeHeapSize=5836300 -XX:NonProfiledCodeHeapSize=122910970 -XX:ProfiledCodeHeapSize=122910970 -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseG1GC

VM Arguments:
jvm_args: -Xms20M -Xmx20M -Xmn10M -XX:+UseG1GC -XX:MaxGCPauseMillis=200
java_command: czhao.study.jvm.TestCase01GCTest testParam
java_class_path (initial): .:/usr/java/jdk1.8.0_131/lib:/usr/java/jdk1.8.0_131/jre/lib
Launcher Type: SUN_STANDARD

jinfo 的输出内容有三部分,分别是:

  • Java System Properties:JVM 运行时的环境变量
  • VM Flags:生效的虚拟机参数配置
  • VM Arguments:启动 JVM 时传入的参数、命令、及当时的对应会话的环境变量

这里要尤其注意的是,如果系统环境上不止一个 JDK,比如这里的例子,实际有两个 JDK,一个 Java8 一个 Java11,那么运行 JVM 时使用的 JDK 版本应该看 Java System Properties 里的 java.runtime.version,而不是 VM Arguments 里的 java_class_path (initial)

可以使用 <pid> 只查看 JVM 运行时的环境变量。

可以使用 jinfo -flags <pid> 只查看生效的虚拟机参数配置,比如 jinfo -flags 10517

可以使用 jinfo -flag <name> <pid> 查看指定参数的值,比如 jinfo -flag MaxHeapSize 10517

可以使用 jinfo -flag [+|-]<name> <pid> 实时开启或关闭某个可以在运行时改变的参数,比如 jinfo -flag +HeapDumpOnOutOfMemoryError 10517

可以使用 jinfo -flag <name>=<value> 实时修改某个可以在运行时改变的参数的值,比如 jinfo -flag MaxHeapFreeRatio=75 10517

通过命令 java -XX:+PrintFlagsFinal <pid> | grep manageable 查看哪些参数可以在运行时改变。

jcmd

jcmd 提供了上述命令行工具的统一使用方式,如下表所示:

命令 jcmd 命令 jhsdb 命令
jps -lm jcmd -
jmap -dump jcmd pid GC.heap_dump fillpath…..hpro jhsdb jmap –binaryheap –pid
jmap -histo jcmd pid GC.class_histogram jhsdb jmap –histo –pid
jstack jcmd pid Thread.print jhsdb jstack –locks –pid
jinfo -sysprops jcmd pid VM.system_properties jhsdb info –sysprops –pid
jinfo -flags jcmd pid VM.flags jhsdb jinfo –flags –pid

与 jcmd 具有类似功能的还有 jhsdb,也一起列在下面的表中。jhsdb 还提供了图形化功能,在下一节介绍。

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
印 jvm 进程,查看 pid
jcmd -l

#dump 快照
jcmd pid GC.heap_dump fillpath.....hprof

#查看 java 系统属性
jcmd pid VM.system_properties

#查看 jvm 属性
jcmd pid VM.flags

#查看 jvm 启动指定属性
jcmd pid VM.command_line

#强制调用已经失去引用的对象的 finalize 方法
#system.runFinalization()
jcmd pid GC.run_finalization

#告诉垃圾收集器打算进行垃圾收集,而垃圾收集器进不进行收集是不确定的
#System.gc()
jcmd pid GC.run

#查看 jdk 属性
jcmd pid PerfCounter.print

#查看 jvm 版本
jcmd pid VM.version

#查看系统中类统计信息
jcmd pid GC.class_histogram

#查看线程堆栈信息。
jcmd pid Thread.print

jhsdb

jhsdb 除了命令以外,还提供了图形化功能来监视分析 JVM 状况。

JDK8 没有这个命令 jhsdb 使用如下命令开启目标 JVM 的图形化分析界面:

1
jhsdb hsdb --pid <pid>

同样的,jhsdb 使用时,也要注意用户、版本是否与目标 JVM 一致。

打开后首先有一个线程窗口,展现当前的线程信息。

另外 tools 菜单下有很多功能按钮,比如 Class Browser 可以查看所有类信息,Heap Parameters 可以输出当前堆内存分区使用情况就像下面这样:

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
G1 Heap:
regions = 20
capacity = 20971520 (20.0MB)
used = 15204416 (14.50006103515625MB)
free = 5767104 (5.49993896484375MB)
72.50030517578125% used
G1 Young Generation:
Eden Space:
regions = 0
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
Survivor Space:
regions = 1
capacity = 1048576 (1.0MB)
used = 1048576 (1.0MB)
free = 0 (0.0MB)
100.0% used
G1 Old Generation:
regions = 14
capacity = 14680064 (14.0MB)
used = 14155840 (13.50006103515625MB)
free = 524224 (0.49993896484375MB)
96.42900739397321% used

还有很多其他功能,大家可以慢慢尝试。

jconsole

jconsole 是一款对 JVM 的可视化监视管理工具,通过它我们可以实时地监视 JVM 的内存、线程、类加载等信息的变化趋势。

使用下面的命令启动:

1
jconsole

同样的,jconsole 使用时,也要注意用户、版本是否与目标 JVM 一致。

jconsole 启动后有一个选择 JVM 进程的界面,选择一个 JVM 后即可看到界面。

如果要连接一个远程机器上的 JVM,那么需要在目标机器的 JVM 上增加以下参数:

1
2
3
4
5
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.port=<port>
-Djava.rmi.server.hostname=<ip>

如果想要使用密码认证和 ssl 保证通信安全,请自行搜索。

Java9 后需要单独下载的 JDK 工具

Java9 之后,部分 JVM 工具被移出了 JDK 工具包,需要单独去安装。这里介绍两个比较好用的工具。

visualVM

visualVM 是一个类似 jconsole,但是比 jconsole 的功能更强大丰富的 JVM 监视工具,还可以用来分析 jmap 的堆转储快照文件。它也可以连接远程 JVM,方法与 jconsole 的远程 JVM 一样。

visualVM 的下载地址是:

1
https://visualvm.github.io/download.html

下载后启动:

1
./visualvm --jdkhome /usr/java/jdk-11.0.7+10

同样的,visualVM 使用时,也要注意用户、JDK 版本是否与目标 JVM 一致。

visualVM 监视信息比 jconsole 更丰富,而且它还可以直接导入 jmap 的堆转储快照文件,并按照你需要的维度做一些简单的排序展示。

JMC

JMC,Java Mission Control,是另一个很强大的 JVM 监视工具,和 visualVM、jconsole 一样,它可以监视 JVM 的各种数据。除此以外,它还提供了强大的 飞行记录器 功能,记录一段时间内 JVM 的各种信息,包括内存、代码、线程、IO、事件等等的记录,然后基于这些信息做性能分析。

JMC 下载地址如下:

1
https://www.oracle.com/java/technologies/javase/products-jmc7-downloads.html

本地启动命令:

1
./jmc

页面启动后,左侧会显示本地 JVM,如果要连接远程 JVM,可以在 文件 --> 连接 菜单中创建新连接。远程 JVM 的参数配置与 jconsole 一样。

直接点击左侧菜单的目标 JVM 即可打开实时监视页面,双击左侧菜单该 JVM 下层的 飞行记录器 即可开始一次飞行记录,结束后会自动给出分析报告。

Flight Recorder 飞行记录,简称 JFR,以前是商业 JDK 的特性,后来在 JDK11 中开源,通常可以通过 JVM 启动参数 -XX:StartFlightRecording 开启,或者通过 jcmd 相关命令录制。这里通过 JMC 工具可以可视化录制飞行记录。

JFR 是一种用于收集关于正在运行的 Java 应用程序的诊断和分析数据的工具。它集成到 Java 虚拟机(JVM)中,几乎不会造成性能开销,因此即使在负载非常大的生产环境中也可以使用它。它收集 JVM 的各种事件信息,包括:磁盘 IO、GC、线程 sleep、线程 wait、Socket read/write 等。JFR 就如同飞机上的黑匣子,通过收集的这些事件的详细信息能够更加深入了解程序的内部运行过程,这是很多其他工具所不具备的。

除了实时 JVM 监视和飞行记录分析以外,JMC 也可以直接打开并分析 jmap 的堆转储快照文件

其他工具

MAT

MAT,全称 Memory Analyzer Tool,它是一个傻瓜式的堆转储快照文件分析工具,既可以自己生成堆转储快照文件,也可以直接分析 jmap 命令导出的快照文件。

MAT 工具可以提供以下分析:

  • Histogram:列出内存中的对象,对象的个数以及大小
  • Dominator Tree:列出最大的对象以及其依赖存活的 Object
  • Top Consumers : 通过图形列出最大的 object
  • duplicate classes :检测由多个类装载器加载的类
  • Leak Suspects :内存泄漏分析
  • Top Components:列出大于总堆数的百分之 1 的报表
  • Component Report:分析属于同一个包或者被同一个类加载器加载的对象状况

其中最常用的就是 Leak Suspects 内存泄漏分析。

MAT 需要单独下载安装,下载地址:

1
https://www.eclipse.org/mat/downloads.php

MAT 是 eclipse 系的工具,类似与 Jprofiler 之于 IDEA,但它是免费的,不像 Jprofiler 还收费。。。

下载到本地解压后直接运行 MemoryAnalyzer 即可。

使用也很简单,首页点击 Open heap dump,然后选择对应的堆转储快照文件即可。

我们一般是在 JVM 启动参数添加 -XX:+HeapDumpOnOutOfMemoryError 让 JVM 在发生内存溢出异常时自动 dump 堆快照文件,所以分析时最重要的就是找出数量和空间消耗最大的对象信息,并通过调用堆栈信息查找可能发生内存泄露的代码所在。

而通过 MAT 的 Leak Suspects 功能,就能直接给出一个内存泄露分析报告,更多相关信息请查看官方文档:

http://wiki.eclipse.org/Memory

IBM HeapAnalyzer

MAT 在分析内存泄露时,虽然能快速定位发生内存溢出异常时占用空间最多的对象,但这些对象往往是很底层的对象,我们要通过堆栈调用去找到真正代码中产生泄露的地方。而 MAT 貌似没有直观展现调用关系树的功能,这时我们可以使用 IBM HeapAnalyzer 这个工具。

IBM HeapAnalyzer 的下载和使用参考下面的地址:

1
https://www.ibm.com/support/pages/node/1109955?mhsrc=ibmsearch_a&mhq=heapanalyzer

它与 MAT 一样,也可以自动做内存泄露分析,比 MAT 更好的地方是,它对堆栈调用做了可视化的转换,可以更直观地看到调用关系树。

IBM HeapAnalyzer 最大的问题是,它已经很久没有更新维护了。。。

Jprofiler

JProfiler 是由 ej-technologies 公司开发的一款性能瓶颈分析工具。它是一款优秀的商业软件,功能非常丰富,因此具备一些免费软件所不具备的功能。Jprofiler 提供的主要功能有内存视图、CPU 视图、线程视图、堆遍历器(Heap Walker)等。

它与 MAT,IBM HeapAnalyzer 一样,可以用于分析堆转储快照文件。但由于收费,这里就不介绍了。

Arthas

前面所有的 JVM 工具,都是基于 JVM 自己提供的 MBeans/JMX 技术,或者 JFR 技术去监视 JVM 状态。要么是监视 JVM 运行时数据,要么是 OOM 之后的堆转储快照文件的离线分析。

如果我们要找到一个 JVM 运行时的性能瓶颈所在,我们需要监视运行时内存、线程、CPU 等资源的变化,并找到对应时间段的对象或线程来定位具体比较消耗资源的代码。这种方式实际操作起来还是很麻烦的。

而 Arthas 就是一个在实时监视跟踪具体方法方面特别强大的一种 JVM 在线调试工具。Arthas 提供了在线的方法级别的监视跟踪功能。比如 monitor/watch/trace 等指令,通过字节码增强技术,直接在代码字节码层面做插桩,实现对运行时方法调用链、耗时、返回数据等信息的动态实时监视。

Arthas 的安装与使用可以直接参考官方文档,上手很简单:

1
2
3
https://arthas.aliyun.com/doc/index.html
https://arthas.aliyun.com/doc/quick-start.html
https://arthas.aliyun.com/doc/advanced-use.html

除了 JMC 的 JFR 飞行记录,其他 JVM 工具所能监视的信息,基本上 Arthas 也都具备对应功能。而除了这些 JVM 信息查看、运行时状况监视。以及方法的实时监视跟踪之外,Arthas 还有在线编译与反编译的功能,对于某些场景下的临时验证,或确认部署版本是否正确等特殊需求而言,十分方便。

但 arthas 在使用时,会对 JVM 的字节码造成入侵,会占用部分资源,对系统整体性能有一定的影响。所以相比于其他工具,arthas 其实是一个开发人员的调试工具,而不是 JVM 运维工具。

JVM 工具总结

JVM 运维与调试工具当然并不是仅仅只有本章节所列举的这些,但一般而言,这些工具掌握部分也就满足平时的需要了。这里对它们进行一个简单的总结。

  • 当你只是简单地查看 JVM 运行时的状况时,你可以直接使用 JDK 自带的那些工具命令,比如 jpsjinfo 等等。
  • 当你需要在 OOM 时查看内存泄露原因时,可以直接在 JVM 参数中配置 OOM 自动 dump 堆转储快照文件,并配合 jmap 等工具手动或定时周期性地 dump 堆快照。
  • 当你想实时监视 JVM 的内存、线程、CPU 等资源消耗趋势时,你可以使用 jconsolevisualVMJMC 等工具。
  • 当你想全面监视 JVM 各种事件信息,包括磁盘 IO、GC、线程 sleep、线程 wait、Socket read/write 等等,且不想对 JVM 性能带去影响时,你可以通过 JMC 录制 JFR 飞行记录,并在 JMC 中查看报告。
  • 当你需要在方法层面上监视跟踪其调用链路,耗时及返回值时,你可以使用 arthas 这样的在线 JVM 调试工具。

个人认为作为保底手段,无论是 JVM 运维还是开发调试,JDK 自带的那些工具比如 jpsjinfojstatjstack 等,我们都应该要学习如何使用。

对于偏向于 JVM 运维的可视化监视方面,推荐使用 JMC,并尝试录制 JFR 飞行记录。对于专业的 JVM 运维,以及编写各种性能分析报告来说,这个工具很有用。

而对于开发人员来说,目前最推荐的工具是 arthas,基本上 JVM 调试需要的功能它都有。

java 命令行参数

不同的 JDK 版本,对应的 java 命令行参数并不完全一致。java 命令行的参数基本上可以分为三类:

  • 标准选项 Standard options,大部分与 JVM 设置无关,比如 -cp 指定类目录或 jar 文件,比如 -D 设置运行时环境变量,等等。基本上所有 JVM 都支持它们,可以直接通过 java 命令或 java --help 命令查看。
  • 非标准选项 Non-standard options,以 -X 开头,主要是针对 JVM 的一些参数,比如 -Xmx 设置最大堆大小,比如 -Xms 设置初始堆大小,等等。不同 OS 不同 JVM 支持的 -X 选项是不尽相同的,可以通过 java -X 查看。
  • 高级选项 Advanced options,以 -XX 开头,主要是用于 JVM 调优的高级参数,比如 -XX:+HeapDumpOnOutOfMemoryError 开启 OOM 时自动 dump 堆存储文件。一般不建议日常使用。不同 OS 不同 JVM 支持的 -XX 选项也是不尽相同的,通常是运维人员对 JVM 进行调优时才会用到。

关于 Java 命令行参数,目前没有一个比较完整比较好的参考文档,可以看看下面几个文档:

1
2
3
4
5
6
7
8
9
10
11
# JDK7及之前的一些-XX高级选项
https://www.oracle.com/java/technologies/javase/vmoptions-jsp.html

# Java8的java命令行选项
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

# Java11的java命令行选项
https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-3B1CE181-CD30-4178-9602-230B800D4FAE

# VM选项一览,但没有给出实际参数选项格式
https://chriswhocodes.com/vm-options-explorer.html

参考

https://www.jianshu.com/p/ca9b401eb602

https://zhuanlan.zhihu.com/p/351241768?utm_id = 0