> For the complete documentation index, see [llms.txt](https://jun-wang.gitbook.io/learnjava/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://jun-wang.gitbook.io/learnjava/ji-shu-xue-xi/jvm-xue-xi/jvm-chang-yong-gong-ju.md).

# JVM常用工具

jdk给我们提供了很多有用的小工具，就在bin目录下，本质上是对lib/tools.jar中接口的一层封装，下面介绍一下常用的几个工具：

* jps：列出正在运行的虚拟机进程
* jstat：用于监视虚拟机各种运行状态信息
* jinfo：实时查看和调整虚拟机各项参数
* jstack：java堆栈跟踪工具
* jmap：生成heap dump文件

JVM常用工具：Jcosole、Visual VM、Jprofiler、MAT。

## 一、jps

使用jps可以列出所有的虚拟机进程，这个也是使用频率最高的一个命令。常用选项：

| 选项 | 作用                           |
| -- | ---------------------------- |
| -l | 输出主类的全名，如果进程执行的是jar包，输出jar路径 |
| -m | 输出虚拟机进程启动时传递给主类main()函数的参数   |
| -v | 输出虚拟机进程启动时JVM参数              |
| -q | 只输出进程id，省略主类名称               |

**小结：** 使用这个命令可以查看一个java项目启动的入口，让看一个开源工具变得有迹可循。

## 二、jstat

jstat是用于监视虚拟机各种运行状态信息的工具，它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据，在没有GUI图形界面，只提供了纯文本控制台环境的服务器上，它是运行期定位虚拟机性能问题的首选。

比如需要每250毫秒查询一次进程5190的垃圾收集状况，一共查询20次，那么命令应是：

```shell
jstat -gc 5190 250 20
```

jstat的作用主要是3类：类装载，垃圾收集，运行期编译情况，具体可以参考以下选项：

| 选项                | 作用                                                       |
| ----------------- | -------------------------------------------------------- |
| -class            | 监视类装载、卸载数量、总空间以及类装载所耗费的时间                                |
| -gc               | 监视java堆情况，包括Eden区，两个survivor区，老年代，永久代等的容量，已用空间，GC时间合计等信息 |
| -gccapacity       | 与gc类似，但输出主要关注java堆各个区域使用到的最大，最小空间                        |
| -gcutil           | 与gc类似，但输出主要关注j已使用空间占总空间的百分比                              |
| -gccause          | 与-gcutil功能一样，但是会额外输出导致上一次GC产生的原因                         |
| -gcnew            | 监视新生代GC状况                                                |
| -gcnewcapacity    | 与gcnew类似，但输出主要关注使用到的最大，最小空间                              |
| -gcold            | 监视老年代GC状况                                                |
| -gcoldcapacity    | 与gcold类似，但输出主要关注使用到的最大，最小空间                              |
| -gcmetacapacity   |                                                          |
| -compiler         | 输出JIT编译器编译过的方法、耗时等信息                                     |
| -printcompilation | 输出已被JIT编译的方法                                             |

使用jstat纯文本工具确实不如VisualVM等可视化监视工具直接以图表那样展示方便，但在没有GUI图形界面的系统是一个不错的选择。

## 三、jinfo

jinfo可以实时查看和调整虚拟机各项参数，使用jps -v可以查看虚拟机启动时指定的参数列表，但如果想看未被显示指定的参数的系统默认值，就可以使用jinfo了。

| 选项       | 作用                                                                                                                                   |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| flags    | 查看所有的虚拟机默认参数。注意：在运行时可能会遇到Operation not permitted错误，目前还没有找解决办法，可以使用**java -XX:+PrintFlagsFinal** 同样可以查看虚拟机的全部参数。比如：jinfo -flags 19891 |
| flag     | 查看单个虚拟机参数，比如：jinfo -flag AdaptiveSizeDecrementScaleFactor 5190。可以使用-flag \[+\|-] name 或者 -flag name=value修改一部分运行期可写的虚拟机参数值。          |
| sysprops | 查看虚拟机System.getProperites()的内容。                                                                                                      |

## 四、jstack

jstack命令用于生成虚拟机当前时刻的线程快照。线程快照就是当前虚拟机内每一条线程正在执行的方法栈的集合，生成线程快照的主要目的是定位线程出现长时间停顿的原因，如线程间死锁，死循环、请求外部资源导致的长时间等待都是导致线程长时间停顿的常见原因。线程出现停顿的时候通过jstack来查看各个线程的调用堆栈，就可以知道没有响应的线程到底在后台做些什么事情，或者等待着什么资源。

**命令格式：** jstack \[option] vmid

| 选项 | 作用                       |
| -- | ------------------------ |
| -F | 当正常输出的请求不被响应时，强制输出线程堆栈   |
| -l | 除堆栈外，显示关于锁的附加信息          |
| -m | 如果调用到本地方法的话，可以显示C/C++的堆栈 |

```shell
# 示例
jstack 21435
# 查找死循环案例
# 1.使用top找出CPU占用最大的进程{pid}
# 2.使用top -H -p {pid}找出CPU占用最大的线程{tid}
# 3.jstack -l -F {pid} > test.log 打印线程堆栈
# 4.grep {tid} test.log 找出占用CPU最大的线程目前正在运行哪一行
# 5.分析代码
```

## 五、jmap

jmap命令用于生成堆转储快照，一般称为heapdump或dump文件。但jmap的作用不仅仅是为了获取dump文件，它还可以查询finalize执行队列、java堆和永久代的详细信息，如空间使用率、当前用的是哪种收集器等。

**命令格式：** jmap \[option] vmid

| 选项             | 作用                                                                       |
| -------------- | ------------------------------------------------------------------------ |
| -dump          | 生成Java堆转储快照，格式:-dump:\[live, ]format=b,file= vmid。live参数说明是否之dump出存活的对象。 |
| -finalizerinfo | 显示出F-Queue中等待Finalizer线程执行finalize方法的对象                                  |
| -heap          | 显示java堆详细信息，如使用哪种回收器、参数配置、分代状况等。                                         |
| -histo         | 显示堆中对象统计信息，包括类、实例数量、合集容量                                                 |
| -clstats       | 打印类加载器信息                                                                 |
| -F             | 当虚拟机进程对-dump没有响应时，可使用这个参数强制生成dump快照。                                     |

**实例：**

```shell
# -dump  生成之后可以使用JVisualVM分析dump文件
jmap -dump:format=b,file=test.log 8188
Heap dump file created
# head
jmap -heap 29766
Attaching to process ID 29766, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.202-b08

using thread-local object allocation.
Garbage-First (G1) GC with 18 thread(s)   # 使用的垃圾收集器是G1

Heap Configuration:  # 堆内存配置情况
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 4294967296 (4096.0MB)
   NewSize                  = 1363144 (1.2999954223632812MB)
   MaxNewSize               = 2576351232 (2457.0MB)
   OldSize                  = 5452592 (5.1999969482421875MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 67108864 (64.0MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 1048576 (1.0MB)

Heap Usage:
G1 Heap:  # 堆内存实际使用情况
   regions  = 4096
   capacity = 4294967296 (4096.0MB)
   used     = 141292960 (134.74746704101562MB)
   free     = 4153674336 (3961.2525329589844MB)
   3.2897330820560455% used
G1 Young Generation:  # 新生代内存使用情况
Eden Space:
   regions  = 55
   capacity = 148897792 (142.0MB)
   used     = 57671680 (55.0MB)
   free     = 91226112 (87.0MB)
   38.732394366197184% used
Survivor Space:
   regions  = 1
   capacity = 1048576 (1.0MB)
   used     = 1048576 (1.0MB)
   free     = 0 (0.0MB)
   100.0% used
   
   G1 Old Generation:  # 老年代内存使用情况
   regions  = 83
   capacity = 106954752 (102.0MB)
   used     = 82572704 (78.74746704101562MB)
   free     = 24382048 (23.252532958984375MB)
   77.20339905981923% used

17541 interned Strings occupying 1547760 bytes.
```

## 六、jhat

jhat是虚拟机堆转储快照分析工具，与jmap配合使用，用来分析jmap生成的堆转储快照。jhat内置了一个微型的HTTP/HTML服务器，生成分析结果后可以在浏览器查看。实际应用中一般使用更加完善的分析工具。

```shell
$ jhat test.log 
Reading from test.log...
Dump file created Fri Mar 13 12:34:51 CST 2020
Snapshot read, resolving...
Resolving 125137 objects...
Chasing references, expect 25 dots.........................
Eliminating duplicate references.........................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
 
# 通过http://localhost:7000端口访问分析结果
```

分析结果是以包为单位进行分组显示，分析内存泄漏问题主要会使用其中的`heap histogram`（与hmap -histo功能一样）和OQL页签的功能，前者可以找到内存中总容量最大的对象，后者是标准的对象查询语言。

## 七、可视化工具Java VisualVM

VisualVM是目前为止JDK发布的功能最强大的运行监视和故障处理程序。通过插件支持，VisualVM可以做到：

* 显示虚拟机进程以及进程的配置、环境信息（jps，jinfo）
* 监视应用程序的CPU、GC、堆、方法区以及线程的信息（jstat，jstack）
* dump以及分析堆转储快照（jmap，jhat）
* 方法级的程序运行性能分析，找出被调用最多、运行时间最长的方法
* 离线程序快照：收集程序的运行时配置、线程dump、内存dump等信息建立一个快照，可以将快照发送开发者处进行Bug反馈
* ......

### 7.1 线程Dump

在指定的进程上右键选择线程dump就会生成一份线程dump，类似下面，里面列出个各个线程的状态，比如从下面的线程dump中可以发现主线程main阻塞在了accept这里。

```verilog
···
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000005119000 nid=0x1820 in Object.wait() [0x0000000020bbf000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000006c2238738> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Unknown Source)
        at java.lang.ref.Reference.tryHandlePending(Unknown Source)
        - locked <0x00000006c2238738> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Unknown Source)

   Locked ownable synchronizers:
        - None

"main" #1 prio=5 os_prio=0 tid=0x0000000005023000 nid=0x3fd4 runnable [0x000000000501e000]
   java.lang.Thread.State: RUNNABLE
        at java.net.DualStackPlainSocketImpl.accept0(Native Method)
        at java.net.DualStackPlainSocketImpl.socketAccept(Unknown Source)
        at java.net.AbstractPlainSocketImpl.accept(Unknown Source)
        at java.net.PlainSocketImpl.accept(Unknown Source)
        - locked <0x00000006c220b568> (a java.net.SocksSocketImpl)
        at java.net.ServerSocket.implAccept(Unknown Source)
        at java.net.ServerSocket.accept(Unknown Source)
        at com.wangjun.io.bio.TimeServer.main(TimeServer.java:30)

   Locked ownable synchronizers:
        - None
...
```
