2020-heibaiying-JVM 性能监控之命令行工具
JVM 性能监控之命令行工具
一、简介
在 JDK 安装目录的 bin
文件夹下,除了提供有 javac
、java
这两个常用的编译和运行工具外,还提供了一系列命令行工具用于 JVM 的性能监控和故障诊断,常用的命令如下:
二、jps
jps(JVM Process Status Tool)用于列出正在运行的虚拟机进程的主类名称和 LVMID(Local Virtual Machine Identifier,本地虚拟机唯一标识),这里得到的 LVMID 是进行后续其它查询的基础。示例如下:
C:/Users>jps
10848 Main
14560 Jps
7040 Launcher
11572
9492 DeadLockTest
7868 JConsole
可选参数有 -v
,用于输出虚拟机进程启动时的 JVM 参数。
三、jstat
jstat(JVM Statistics Monitoring Tool)用于监视虚拟机的运行状态。使用格式如下:
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
其中 option
的所有可选值如下:
选项 | 作用 |
---|---|
-class | 监视类加载、卸载数量、总空间以及类装载所耗费的时间 |
-gc | 监视 Java 堆状况,包括 Eden 区、2 个 Survivor 区、老年代的容量、已用空间、垃圾收集时间等信息 |
-gccapacity | 与 -gc 基本相同,但主要关注的是 Java 堆各个区域使用到的最大、最小空间 |
-gcutil | 与 -gc 基本相同,但主要关注的是已使用空间占总空间的百分比 |
-gccause | 与 -gcutil 基本相同,但是会额外输出上一次垃圾回收的原因 |
-gcnew | 监视新生代垃圾回收的状况 |
-gcnewcapacity | 与 -gcnew 基本相同,但主要关注的是使用到的最大、最小空间 |
-gcold | 监视老年代垃圾回收的状况 |
-gcoldcapacity | 与 -gcold 基本相同,但主要关注的是使用到的最大、最小空间 |
-compiler | 输出即时编译器编译过的方法、耗时等信息 |
-printcompilation | 输出已经被即时编译的方法 |
命令行中的 interval
表示监控的时间间隔,count
表示监控次数。示例如下:
jstat -gc 9492 3s 5 # 每3s输出一次,一共输出5次
输出信息中各个参数含义分别如下:
- S0C:survivor 0 的容量大小,单位 kB;
- S1C:survivor 1 的容量大小,单位 kB;
- S0U:survivor 0 已使用的空间大小,单位 kB;
- S1U:survivor 1 已使用的空间大小,单位 kB;
- EC:Eden 区的容量大小,单位 kB;
- EU:Eden 区已使用的空间大小,单位 kB;
- OC:老年代的容量大小,单位 kB;
- OU:老年代已使用的空间大小,单位 kB;
- MC:Metaspace 容量大小,单位 kB;
- MU:Metaspace 已使用的空间大小,单位 kB;
- CCSC:压缩类的空间大小,单位 kB;
- CCSU:压缩类已使用的空间大小,单位 kB;
- YGC:年轻代垃圾回收的次数;
- YGCT: 年轻代垃圾回收所消耗的时间;
- FGC:老年代垃圾回收的次数;
- FGCT:老年代垃圾回收所消耗的时间;
- GCT:垃圾回收所消耗的总时间。
以上是 option 为 -gc
时的输出结果,不同 option 的输出结果是不同的,所有输出结果及其参数解释可以参考官方文章: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html
四、jinfo
jinfo(Configuration Info for Java)的作用是实时查看和调整虚拟机的各项参数。使用格式如下:
jinfo [option] <pid>
其中 option
支持以下可选项:
- -flag name :输出指定的虚拟机参数的值;
- -flag [+|-]name :启用或禁用指定名称的虚拟机参数;
- -flag name=value :设置虚拟机参数的值;
- -flags :以键值对的方式输出 JVM 的相关属性;
- -sysprops:以键值对的方式输出 Java 相关的系统属性。
示例如下:
jinfo -flags 13604
jinfo -flag CMSInitiatingOccupancyFraction 13604
五、jmap
jmap(Memory Map for Java)命令主要用于生成堆转储快照(一般称为 heapdump 或 dump 文件)。除此之外,它还可以用来查询 finalize 执行队列、Java 堆和方法区的详细信息,如空间使用率、当前使用的收集器等。 使用格式如下:
jmap [option] <pid>
其中 option
支持以下可选项:
选项 | 作用 |
---|---|
-dump:[live,]format=b,file= | 生成 Java 堆转储快照,其中 live 用于指明是否只 dump 出存活的对象 |
-finalizerinfo | 显示在 F-Queue 中等待 Finalizer 线程执行 finalize 方法的对象。只在 Linux/Solaris 平台下有效 |
-heap | 显示 Java 堆详细信息,如使用哪种回收器、参数配置、分代状况等。只在 Linux/Solaris 平台下有效 |
-histo[:live] | 显示堆中对象的统计信息,包括类、实例数量、合计容量 |
-permstat | 以 ClassLoader 为统计口径显示永久代内存状态。只在 Linux/Solaris 平台下有效 |
-F | 当虚拟机进程堆 -dump 选项没有响应时,可使用这个选项强制生成 dump 快照。 只在 Linux/Solaris 平台下有效 |
示例如下:
jmap -dump:format=b,file=test.bin 3260
六、jhat
jhat(JVM Heap Analysis Tool)命令主要用来分析 jmap 生成的堆转储快照。 假设我们有如下一段程序:
public class StackOverFlowTest {
private static List<StackOverFlowTest> list = new ArrayList<>();
public static void main(String[] args) throws InterruptedException {
while (true) {
list.add(new StackOverFlowTest());
Thread.sleep(10); //因为只是演示,所以休眠一下,避免生成的堆转储文件过大,导致分析时间过长
}
}
}
其最终会抛出 java.lang.OutOfMemoryError: Java heap space
异常,意味着在 JVM 堆上发生了内存溢出。在程序运行期间,我们可以使用上面的 jmap 命令生成堆转储快照,并使用 jhat 命令进行分析:
jhat 命令最终的分析结果会以网页的方式进行提供,端口为 7000,界面如下:
jhat 分析的结果并不够直观,因此我们还可以借助第三方工具来分析堆转储快照,这里以 JProfiler 为例,该软件可以直接从官网下载并安装,安装完成后,点击 session
选项卡,并使用 Open Snapshot
打开 jmap 命令生成的堆转储快照:
之后程序会自动进行分析,分析结果如下:
通过以上可视化的统计结果,我们就可以很快定位到导致内存溢出的原因。
七、jstack
jstack(Stack Trace for Java)命令用于生成虚拟机的线程快照(一般称为 threaddump 或者 javacore 文件)。线程快照就是每一条线程正在执行的方法堆栈的集合,线程快照可以用于定位线程长时间停顿的原因,如死锁、死循环和长时间挂起等。其使用格式如下:
jstack -F [-m] [-l] <pid>
各选项的作用如下:
选项 | 作用 |
---|---|
-F | 当正常输出的请求不被响应时,强制输出线程堆栈 |
-m | 除堆栈外,显示关于锁的附加信息 |
-l | 如果有调用本地方法的话,则可以显示 C/C++ 的堆栈 |
假设我们的程序中存在如下死锁:
public class DeadLockTest {
private static final String a = "a";
private static final String b = "b";
public static void main(String[] args) {
new DeadLockTest().deadlock();
}
private void deadlock() {
new Thread(() -> {
synchronized (a) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
}
}
}).start();
new Thread(() -> {
synchronized (b) {
synchronized (a) {
}
}
}).start();
}
}
此时使用 jstack 分析就能很快的定位到问题所在,示例如下:
jstack 8112
输出结果如下:
从输出中结果中可以看出,出现了一个死锁,该死锁由线程 Thread-0 和 Thread-1 导致,原因是 Thread-0 锁住了对象 <0x00000000d6d8d610>
,并尝试获取 <0x00000000d6d8d640>
对象的锁;但是 Thread-0 却恰恰相反,锁住了对象 <0x00000000d6d8d640>
,并尝试获取 <0x00000000d6d8d610>
对象的锁,由此导致死锁。
参考资料
- 主要参考自:周志明 . 深入理解 Java 虚拟机(第 3 版). 机械工业出版社 , 2019-12 ,想要深入了解虚拟机的话,推荐阅读原书。
- 官方文档:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/s11-troubleshooting_tools.html#sthref327