Full-Stack-Notes/notes/JVM_性能监控之命令行工具.md
2020-05-25 11:28:56 +08:00

263 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# JVM 性能监控之命令行工具
<nav>
<a href="#一简介">一、简介</a><br/>
<a href="#二jps">二、jps</a><br/>
<a href="#三jstat">三、jstat</a><br/>
<a href="#四jinfo">四、jinfo</a><br/>
<a href="#五jmap">五、jmap</a><br/>
<a href="#六jhat">六、jhat</a><br/>
<a href="#七jstack">七、jstack</a><br/>
</nav>
## 一、简介
在 JDK 安装目录的 `bin` 文件夹下,除了提供有 `javac``java` 这两个常用的编译和运行工具外,还提供了一系列命令行工具用于 JVM 的性能监控和故障诊断,常用的命令如下:
## 二、jps
jpsJVM Process Status Tool用于列出正在运行的虚拟机进程的主类名称和 LVMIDLocal Virtual Machine Identifier本地虚拟机唯一标识这里得到的 LVMID 是进行后续其它查询的基础。示例如下:
```shell
C:/Users>jps
10848 Main
14560 Jps
7040 Launcher
11572
9492 DeadLockTest
7868 JConsole
```
可选参数有 `-v` ,用于输出虚拟机进程启动时的 JVM 参数。
## 三、jstat
jstatJVM Statistics Monitoring Tool用于监视虚拟机的运行状态。使用格式如下
```shell
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` 表示监控次数。示例如下:
```shell
jstat -gc 9492 3s 5 # 每3s输出一次一共输出5次
```
<div align="center"> <img src="https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jstat_gc.png"/> </div>
输出信息中各个参数含义分别如下:
- **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
jinfoConfiguration Info for Java的作用是实时查看和调整虚拟机的各项参数。使用格式如下
```shell
jinfo [option] <pid>
```
其中 `option ` 支持以下可选项:
- **-flag name** :输出指定的虚拟机参数的值;
- **-flag [+|-]name** :启用或禁用指定名称的虚拟机参数;
- **-flag name=value** :设置虚拟机参数的值;
- **-flags** :以键值对的方式输出 JVM 的相关属性;
- **-sysprops**:以键值对的方式输出 Java 相关的系统属性。
示例如下:
```java
jinfo -flags 13604
jinfo -flag CMSInitiatingOccupancyFraction 13604
```
<div align="center"> <img src="https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jinfo.png"/> </div>
## 五、jmap
jmapMemory Map for Java命令主要用于生成堆转储快照一般称为 heapdump 或 dump文件。除此之外它还可以用来查询 finalize 执行队列、Java 堆和方法区的详细信息,如空间使用率、当前使用的收集器等。 使用格式如下:
```shell
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 快照。<br/>只在 Linux/Solaris 平台下有效 |
示例如下:
```shell
jmap -dump:format=b,file=test.bin 3260
```
<div align="center"> <img src="https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jmap.png"/> </div>
## 六、jhat
jhatJVM Heap Analysis Tool命令主要用来分析 jmap 生成的堆转储快照。 假设我们有如下一段程序:
```java
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 命令进行分析:
<div align="center"> <img src="https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jhat.png"/> </div>
jhat 命令最终的分析结果会以网页的方式进行提供,端口为 7000界面如下
<div align="center"> <img src="https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jhat_web.png"/> </div>
jhat 分析的结果并不够直观,因此我们还可以借助第三方工具来分析堆转储快照,这里以 JProfiler 为例,该软件可以直接从[官网](https://www.ej-technologies.com/products/jprofiler/overview.htm)下载并安装,安装完成后,点击 `session` 选项卡,并使用 `Open Snapshot` 打开 jmap 命令生成的堆转储快照:
<div align="center"> <img src="https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jprofiler-1.png"/> </div>
之后程序会自动进行分析,分析结果如下:
<div align="center"> <img src="https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jprofiler-2.png"/> </div>
通过以上可视化的统计结果,我们就可以很快定位到导致内存溢出的原因。
## 七、jstack
jstackStack Trace for Java命令用于生成虚拟机的线程快照一般称为 threaddump 或者 javacore 文件)。线程快照就是每一条线程正在执行的方法堆栈的集合,线程快照可以用于定位线程长时间停顿的原因,如死锁、死循环和长时间挂起等。其使用格式如下:
```shell
jstack -F [-m] [-l] <pid>
```
各选项的作用如下:
| 选项 | 作用 |
| ---- | ----------------------------------------------- |
| -F | 当正常输出的请求不被响应时,强制输出线程堆栈 |
| -m | 除堆栈外,显示关于锁的附加信息 |
| -l | 如果有调用本地方法的话,则可以显示 C/C++ 的堆栈 |
假设我们的程序中存在如下死锁:
```java
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 分析就能很快的定位到问题所在,示例如下:
```shell
jstack 8112
```
输出结果如下:
<div align="center"> <img src="https://gitee.com/heibaiying/Full-Stack-Notes/raw/master/pictures/jstack.png"/> </div>
从输出中结果中可以看出,出现了一个死锁,该死锁由线程 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