JVM虚拟机(1):JVM 监控调优-自带命令
JDK 中自带了一些非常实用的 JVM 性能分析和故障监控的小工具。
也有一些第三方分析工具,如 Arthas。基于开发工具的 JVM 分析工具,如 IDEA 的 VisualVM 插件,Eclipse 的 MAT(Memory Analyzer Tool)。
了解 JVM 内存和性能分析工具及使用,对快速判断和定位线上问题是非常有帮助的。
生产或开发环境中可能会碰到以下问题:
- OOM(OutOfMemoryError):内存溢出(内存不足),没有足够的内存来满足使用,存不下就报错。
- 内存泄漏(Memory Leak):已申请的内存无法释放,GC无法回收就无法再使用,就造成内浪费,频繁的出现就会造成内存泄漏堆积,耗光内存,就可能导致频繁发生 Full GC,就会导致 CPU 使用率过高。
- 线程死锁
- 锁争用(Lock Contention)
- Java 进程消耗过高的 CPU
- ……
这些问题不应该被忽视,而是要深究问题根源,真正地解决问题,而不是简单的重启服务或调大内存。
总览
JDK 自带的命令其实和 Linux 相关的命令很像,个人认为是基于 Linux 命令进行的二次包装,命令名在 Linux 命令增加前缀 j
,例如, ps -> jps
,stat -> jstat
,info -> jinfo
等等。
- jps:可以查看 Java 进程和 JVM 相关参数信息。
- jstat:显示已检测到的 JVM 的性能统计信息,包括类加载,内存,垃圾收集等。
- jstack:打印 Java 进程的线程堆栈信息。将打印完整的类名,方法名,字节码索引和行号。
- jinfo:打印 Java 进程的配置信息。
- jmap:打印进程的共享对象内存映射或堆内存详细信息。
- jhat:创建一个用于分析 Java Heap Dump 文件的 HTTP 服务器。
- jstatd:监控 JVM 和 开启 JVM 的远程监控服务,是一个 RMI 服务。
- jcmd:是个多功能功工具,可以用于导出堆,查看 Java 进程,导出线程,执行 GC 等。
- hprof :不是独立的监控工具,只是一个 Java agent 工具。可用于监控 Java 应用在运行时的 CPU 信息和堆信息。
命令
JDK 自带的分析工具令在安装 JDK 的 bin 中,如: /java/jdk1.8.0_202/bin
所有命令可通过查看命令帮助或命令详情来了解使用,如下:
1 | 命令帮助 |
jps
jps:列出已检测到的 Java HotSpot VM(JVM)的信息,可以查看启动类,传入参数和 JVM 参数等信息,类似于 Linux 的 ps -ef|grep java
。
Java 程序在启动后,会在 java.io.tmpdir
指定的目录下(通常是临时文件夹)里,生成一个类似于 hsperfdata_root
的文件夹,里面有以 Java 进程 ID 号命令的文件,进程信息就需要解析文件。在 Linux 中的目录为 /tmp/hsperfdata_${username}
。
语法
语法:
jps [ options ] [ hostid ]
未指定
hostid
则表示查看本机hostid 格式:
[protocol:][[//]hostname][:port][/servername]
查看远程服务 ,需要在远程服务上有足够的权限来启动
jstatd
服务,实际是开启一个 RMI 服务。输出格式:
processId [ [ classname | JARfilename | "Unknown"] [ arg* ] [ jvmarg* ] ]
参数
参数说明
- -q:只输出 Java 进程 ID
- -m:输出传入 main 方法的参数。嵌入式 JVM 可能输出 null
- -l:输出主类的完全包名 或 Jar 包的完全路径名
- -v:输出传给 JVM 的参数
- -V:只输出 Java 进程ID 和 类名,jps 不带参数的默认项
- -Joption:给 JVM 传递参数。例如,
J-Xms48m
是将启动内存设置为 48M。注意,option 指参数项。
远程连接
- 前提是使用
jstatd
命令启动 RMI 服务。 - 指定协议,IP 和端口:
jps rmi://172.31.97.209:1099
, 1099 是默认端口。
示例
1 | # jps |
jstat
jstat:监视 Java 虚拟机(JVM)统计信息。显示已检测到的 Java HotSpot VM 的性能统计信息。包括类加载,内存,垃圾收集等。
语法
语法:
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
jstat [ generalOption | outputOptions vmid [ interval[s|ms] [ count ] ]
option:参数选项,有两种类型,通用选项 和 统计选项。
-t:显示一个时间戳列(
Timestamp
),单位:秒。vmid:JVM 进程 ID。可以是远程服务的 JVM 进程 ID。
interval:输出时间间隔,单位默认毫秒(ms),可为秒(s),必为正整数。
count:输出次数,必须为正整数。
jstat 显示输出格式是一个表格,每个统计项有不同的列名。
参数
通用选项:
# jstat -options
,列出所有统计选项,见下。统计选项
options:输出项,决定输出那些项的统计信息。
选项 Desc -class 显示类加载的统计信息 -compiler 显示 JVM JIT 编译的统计信息 -gc 显示 GC 时堆内存的统计信息 -gccapacity 显示 GC 时堆内存中不同年代(new,old,metaspace)的统计信息 -gccause 显示引起 GC 事件的摘要信息(与 -gcutil
相同)-gcutil 显示 GC 时的摘要信息,以使用空间的百分比表示 -gcmetacapacity 显示 GC 时 metaspace
(元空间)的内存统计-gcnew 显示 GC 时新生代的统计 -gcnewcapacity 显示 GC 时新生代容量的统计 -gcold 显示 GC 时老年代的统计 -gcoldcapacity 显示 GC 时老年代内存统计 -printcompilation 显示 JVM 编译方法统计信息 -class:类加载
- Loaded:已加载 class 的数量。
- Bytes:已加载的 class 字节大小。
- Unloaded:未加载 class 数量。
- Bytes:未加载的 class 字节大小。
- Time:class 加载的耗时。
-compiler:JIT 编译
- Compiled:执行编译任务的数量。
- Failed:编译失败的数量。
- Invalid:编译无效的数量。
- Time:执行编译的耗时。
- FailedType:最近一次编译失败的编译类型。
- FailedMethod:最近一次编译失败的类名和方法。
-gc:垃圾回收时堆内存信息
S0C:第一个辛存者区容量 (kB).
S1C:第二个辛存者区容量 (kB).
S0U:第一个辛存区的使用大小 (kB).
S1U:第二个辛存区的使用大小 (kB).
EC:当前 Eden 区容量 (kB).
EU:当前 Eden 区使用大小 (kB).
OC:当前老年代容量(KB)
OU:老年代的使用大小(KB)
MC:元空间大小 (kB).
MU:元空间的使用大小 (kB).
CCSC:压缩类空间大小 (kB).
CCSU:压缩类空间的使用大小 (kB).
YGC:年轻代垃圾回收次数.
YGCT:年轻代垃圾回收消耗时间.
FGC:老年代垃圾回收次数.
FGCT:老年代垃圾回收消耗时间.
GCT:垃圾回收总消耗时间.
-gccapacity:年代空间和容间
- NGCMN: 新生代最小容量 (kB).
- NGCMX: 新生代最大容量 (kB).
- NGC: 当前新生代容量 (kB).
- S0C: 当前第一个辛存者区的容量(kB).
- S1C: 当前第二个辛存者区的容量(kB).
- EC: 当前 Eden 区容量 (kB).
- OGCMN: 老年代最小容量 (kB).
- OGCMX: 老年代最大容量 (kB).
- OGC: 当前老年代容量 (kB).
- OC: 当前老年代容间容量 (kB).
- MCMN: 元空间最小容量 (kB).
- MCMX: 元空间最大容量 (kB).
- MC: 当前元空间容量 (kB).
- CCSMN: 压缩类空间最小容量 (kB).
- CCSMX: 压缩类空间最大容量 (kB).
- CCSC: 当前压缩类空间容量 (kB).
- YGC: 年轻代 GC 事件数。
- FGC: 老年代 GC 事件数。
-gccause:收集垃圾回收的摘要信息,包括上次垃圾回收和当前垃圾回收事件的原因。
- LGCC: 上次垃圾回收原因.
- GCC: 当前垃圾回收原因.
-printcompilation:JVM 编译方法统计
- Compiled: 最近的编译方法执行的任务数.
- Size: 最近的编译方法的字节码的字节数.
- Type: 最近的编译方法的编译类型.
- Method: 最近的编译方法的类名和方法名.
示例
1 | GC 的统计信息,进程ID 为3256, 采集3次,每次间隔2秒 |
jstack
jstack:打印 Java 进程的线程堆栈信息。将打印完整的类名,方法名,字节码索引和行号。
语法
1 | jstack [ options ] pid |
- pid:Java 进程 ID 号。可使用命令
jps
查看 Java 进程 ID列表。 - executable:生成 Java 可执行的核心 dump 文件。
- core:打印的栈跟踪核心文件。
- remote-hostname-or-IP:远程调试服务器的主机名或 IP 地址。参见
jsadebugd
。 - server-id:当多个调试服务器在同一远程主机上运行时使用的可选唯一ID。
参数
参数说明
- -F:当使用
jstack [-l] pid
命令无响应时,使用此参数强制堆栈转存为 dump。 - -l:长列表,打印有关锁的附加信息。例如属于 java.util.concurrent ownable synchronizers 的列表。
- -m:打印 Java 和本地 C/C++框架的混合模式堆栈跟踪。无法用于远程调试。
jinfo
jinfo:打印 Java 进程的配置信息。配置信息包括 Java 系统属性和 JVM 命令行标志。如果运行的是 64位 JVM,需要使用 -J-d64
参数项。例如:jinfo -J-d64 -sysprops pid
。
语法
1 | jinfo [ option ] pid |
- pid:Java 进程 ID 号。可使用命令
jps
查看 Java 进程 ID列表。 - executable:生成 Java 可执行的核心 dump 文件。
- core:打印的配置信息核心文件。
- remote-hostname-or-IP:远程调试服务器的主机名或 IP 地址。参见
jsadebugd
。 - server-id:当多个调试服务器在同一远程主机上运行时使用的可选唯一ID。
参数
参数说明
- 无参数:打印命令行标志 和 系统属性
name / value
对。 - -flag name:打印命令名的值。
- -flag name=value:设置命令的值。
- -flag [ + | -] name:开启或关闭 Boolean 命令。
- -flags:打印 JVM 所有标志。
- -sysprops:打印系统属性
name / value
对。
-falg name=value
:不是所有参数可以修改的,只有部分,下面是列出可修改的参数列表:
1 | # java -XX:+PrintFlagsFinal -version |grep manageable |
java -XX:+PrintFlagsFinal -version
输出全部的参数。
示例
1 | Java 进程ID为 31469 的配置信息 |
jmap
jmap:打印进程的共享对象内存映射或堆内存详细信息。如果运行的是 64 位 JVM,可使用 -J-d64
选项。例如,jmap -J-d64 -heap pid
。
语法
1 | jmap [ options ] pid |
- pid:Java 进程 ID 号。可使用命令
jps
查看 Java 进程 ID列表。 - executable:生成 Java 可执行的核心 dump 文件。
- core:打印的配置信息核心文件。
- remote-hostname-or-IP:远程调试服务器的主机名或 IP 地址。参见
jsadebugd
。 - server-id:当多个调试服务器在同一远程主机上运行时使用的可选唯一ID。
参数
无参数:打印 JVM 加载的所有共享对象映射。包括直始地址,映射的大小,共享对象文件的全路径文件。类似于 Linux 的
pmap
命令。-dump:[live,] format=b, file=filename
将堆信息使用
hprof
二进制格式转存为 Dump 文件。可以使用jhat
浏览生成的 Dump 文件。-finalizerinfo:打印关于正在等待完成的对象的信息。
-heap:打印 GC 使用的,头配置和堆的使用情况的堆摘要信息。
**-histo[:live]**::打印堆中对像统计数量。包括每个类的对象数量,内存大小(字节单位)及全限定类名。内部类名有星号(
*
)前缀。若指定了live
子项,则仅统计活动对象。-clstats:打印 Java 堆类加载的统计信息。包括每个类加载器的名称,活动程度,地址,父类加载器及已加载的类的数量和大小。
-F:当
jmap pid
不响应时,此参数与jmap -dump
和jmap -histo
一起使用。此模式不支持live
子项。-Jflag:将标志传递给运行
jmap
命令的 Java 虚拟机。
示例
1 | # jmap 31469 |
jhat
jhat:创建一个用于分析 Java Heap Dump 文件的 HTTP 服务器,可以使用 Web 浏览器浏览堆的 Dump 文件。
生成堆的 Dump 文件方式有:
- 使用
jmap -dump
获取运行时的堆的 Dump。 - 使用
jconsole
选项,通过HotSpotDiagnosticMXBean
获取运行时的堆 Dump。 - 给 JVM 指定
-XX:+HeapDumpOnOutOfMemoryError
,当抛出OutOfMemoryError
错误时会生成堆的 Dump。 - 使用
hprof
命令。一个 Heap/CPU 分析工具,参考 HPROF。
语法
1 | jhat [ options ] heap-dump-file |
- heap-dump-file:Java 二进制 Dump 文件。
参数
- -stack false|true:关闭或开启跟踪对象分配调用栈。如果 Heap Dump 中没有分配站点信息,则必须将此标志设置为 false,默认为 true。
- -refs false|true:关闭或开启对象引用跟踪。默认为 true。
- -port port-number:设置 jhat HTTP 服务的端口,默认为 7000。
- -exclude exclude-file:指定一个文件,该文件列出应从可访问对象查询中排除的数据成员。
- -baseline exclude-file:指定基线 Heap Dump。两个 Heap Dump 中具有相同对象ID的对象都被标记为非新对象,其他对象被标记为新对象,这对于比较两个不同的 Heap Dump 非常有用。
- -debug int:设置 Debug 级别。0 表示无 Debug 输出。
- -Jflag:将标志传递给运行
jhat
令的 Java虚拟机。例如,-J-Xmx512m
使用最大堆大小为 512 MB。
示例
1 | # jhat -port 8000 dump |
jstatd
jstatd:是一个 RMI 服务应用,用于监视 JVM 和 开启 JVM 的远程监控服务,允许远程监视工具连接到本地的 JVM 上。
语法
1 | jstatd [ options ] |
参数
参数说明
- -nr:当找不到现有的 RMI 注册表时,不要尝试在 jstatd 进程中创建内部 RMI 注册表。
- -p port:设置 RMI 服务端口号。
- -n rminame:远程 RMI 对象绑定到 RMI 注册表中的名称。
- -Joption:将 option 传递给 JVM。例如,
-J-Xms48m
将启动内存设置为 48M。
远程连接
安全策略
使该命令可能会报 无访问权限 的错误,需要配置访问策略。错误如下:
1
2
3
4
5
6
7
8Could not create remote object
access denied ("java.util.PropertyPermission" "java.rmi.server.ignoreSubClasses
access denied ("java.util.PropertyPermissmi.server.ignoreSubClasses" "write") :
at java.security.AccessControlContext.checkPermission(AccessControlCont)
at java.security.AccessController.checkPermission(AccessController.java
at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
at java.lang.System.setProperty(System.java:792)
at sun.tools.jstatd.Jstatd.main(Jstatd.java:139)在 Java 安装目录的 bin 文件夹下创建访问策略文件,例如:
jstatd.all.policy
,内容如下:1
2
3
4
5
6
7
8grant codebase "file:${java.home}/../lib/tools.jar" {
permission java.security.AllPermission;
};
#示例:
grant codebase "file:/usr/java/jdk1.8.0_202/lib/tools.jar" {
permission java.security.AllPermission;
};启动服务
下面的 host_ip 就是服务器的 IP 地址,如果是阿里云服务器,就是外网的 IP 地址。
1
2
3
4jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname=host_ip
#后台运行
nohup ./jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname=host_ip &本地验证是否启动成功
1
2
3
4
5
6
7
8
9
10
11# jps -l
3526 org.apache.catalina.startup.Bootstrap
2827 sun.tools.jstatd.Jstatd
31469 /usr/local/activemq-5.15.9//bin/activemq.jar
2926 sun.tools.jps.Jps
# netstat -anp|grep jstatd
tcp6 0 0 :::1099 :::* LISTEN 2827/jstatd
tcp6 0 0 :::14616 :::* LISTEN 2827/jstatd
tcp6 0 0 172.31.97.209:1099 120.229.16.135:9763 ESTABLISHED 2827/jstatd
unix 2 [ ] STREAM CONNECTED 6495795 2827/jstatd注意:通过
netstat
命令可以看到jstatd
服务监听了 2 个端口,1099
是固定端口,远程客户端会先向这个端口请求请求一个RMI
远程对象,再与14616
建立连接,进行点对点通信。14616
是个随机端口,每次重次启动jstatd
服务可能不一样。开放端口
如果服务器本地防火墙配置了端口安全策略,就需要开放这两个端口。
如果是阿里云服务器,需要配置安全策略,开启放两个端口,外部客户端才可以访问。
外部验证是否可访问
外部命令窗口可以用
jps host_ip
来验证是否可访问。Java VisualVM 连接
打开 Java 安装目录下的
bin
文件夹中的jvisualvm.exe
工具,添加远程主机,就会自动列出该主机下运行的所有 Java 进程。
jcmd
jcmd 是个多功能功工具,可以用于导出堆,查看 Java 进程,导出线程,执行 GC 等。必须在运行 JVM 的同一机器上使用。
语法
1 | jcmd [-l|-h|-help] |
如果运行 jcmd 不带参数,或带 -l
参数,将打印用于启动进程的主类 和 正中运行的 Java 进程 ID 列表。
jcmd 拥有 jmap 的大部分功能,且 Oracle 官网也推荐使用此命令代替 jmap。
jcmd 默认参数做 heap dump 之前会做一次 Full GC,内存基本就剩下活着的对象,Full GC 也会对元数据区进行回收,就不易于分析分析元数据内存溢出的情况,可以指定参数不进行 Full GC 的 heap dump。如下:
1 | jcmd [pid] GC.heap_dump -all |
参数
- -l:打印正在运行 Java 的进程号、启动类和命令行参数。
示例
jcmd -l
命令打印 Java 进程号,启动类,运行参数
1
2
3# jcmd -l
12088 org.apache.catalina.startup.Bootstrap start
32697 sun.tools.jcmd.JCmd -l查看进程号虚拟机所支持的 jcmd 命令操作
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# jcmd 12088 help
12088:
The following commands are available:
The following commands are available:
JFR.stop
JFR.start
JFR.dump
JFR.check
VM.native_memory
VM.check_commercial_features
VM.unlock_commercial_features
ManagementAgent.stop
ManagementAgent.start_local
ManagementAgent.start
VM.classloader_stats
GC.rotate_log
Thread.print
GC.class_stats
GC.class_histogram
GC.heap_dump
GC.finalizer_info
GC.heap_info
GC.run_finalization
GC.run
VM.uptime
VM.dynlibs
VM.flags
VM.system_properties
VM.command_line
VM.version
help查看 JVM 启动时间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22根据进程号查看
# jcmd 12088 VM.uptime
12088:
s
根据启动类来查看
# jcmd com.springboot.rabbitmq.Application VM.uptime
12088:
s
查看GC堆信息
# jcmd 12088 GC.heap_info
29452:
PSYoungGen total 228864K, used 83610K [0x00000000eab00000, 0x00000000fa200000, 0x0000000100000000)
eden space 214016K, 39% used [0x00000000eab00000,0x00000000efca6bd0,0x00000000f7c00000)
from space 14848K, 0% used [0x00000000f7c00000,0x00000000f7c00000,0x00000000f8a80000)
to space 14336K, 0% used [0x00000000f9400000,0x00000000f9400000,0x00000000fa200000)
ParOldGen total 196096K, used 12482K [0x00000000c0000000, 0x00000000cbf80000, 0x00000000eab00000)
object space 196096K, 6% used [0x00000000c0000000,0x00000000c0c309e8,0x00000000cbf80000)
Metaspace used 30967K, capacity 31362K, committed 31744K, reserved 1077248K
class space used 3677K, capacity 3793K, committed 3840K, reserved 1048576K打印线程栈
1
# jcmd 12088 Thread.print
查看系统中类的统计信息
1
# jcmd 12088 GC.class_histogram
获取系统属性
1
# jcmd 12088 VM.system_properties
获取 PerData 数据
1
# jcmd 12088 PerfCounter.print
获取 JVM 启动参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18# jcmd 12088 VM.flags
12088:
-BytecodeVerificationLocal :
-BytecodeVerificationRemote :
CICompilerCount=4 :
InitialHeapSize=268435456 :
+ManagementServer :
MaxHeapSize=4263510016 :
MaxNewSize=1420820480 :
MinHeapDeltaBytes=524288 :
NewSize=89128960 :
OldSize=179306496 :
TieredStopAtLevel=1 :
+UseCompressedClassPointers :
+UseCompressedOops :
+UseFastUnorderedTimeStamps :
-UseLargePagesIndividualAllocation :
+UseParallelGC :
hprof
hprof 不是独立的监控工具,只是一个 Java agent 工具。可用于监控 Java 应用在运行时的 CPU 信息和堆信息。
1 | # java -agentlib:hprof=help |
参数使用示例
times 会在 Java 方法的调用前后记录方法的执行时间,进而计算方法的运行时间。
1
# java -agentlib:hprof=cpu=times,interval=10 /HProfTest.java
将应用的堆快照保存到指定文件
c:\core.hprof
1
# java -agentlib:hprof=heap=dump,format=b,file=c:\core.hprof d:\HProfTest.java
查看每个跟踪点上的类所占内存的百分比
1
# java -agentlib:hprof=heap=sites
结果:300010 跟踪点使用内存比 3.71%,20672 个字节
1
2
3
4
5
6
7
8
9
10TRACE 300010:
java.util.Arrays.copyOf(<Unknown Source>:Unknown line)
java.lang.AbstractStringBuilder.ensureCapacityInternal(<Unknown Source>:Unknown line)
java.lang.AbstractStringBuilder.append(<Unknown Source>:Unknown line)
java.lang.StringBuilder.append(<Unknown Source>:Unknown line)
rank self accum bytes objs bytes objs trace name
1 3.71% 3.71% 20672 139 20672 139 300010 char[]
2 3.32% 7.03% 18520 288 18520 288 300311 char[]
3 2.94% 9.97% 16416 2 16416 2 300304 byte[]
其它参考
JVM虚拟机(1):JVM 监控调优-自带命令
http://blog.gxitsky.com/2020/02/03/JVM-01-jdk-command-monitor/