JVM 性能监控与故障处理工具

基础工具

jps:虚拟机进程状态工具

jps 命令格式:jps [options] [hostid]

命令可选项解释:

选项 解释
-q 只输出 LVMID,省略主类的名称
-m 输出传给 main 函数的参数
-l 输出主类的全名,如果进程运行的 JAR 包,则输出 JAR 包的路径
-v 输出虚拟机进程启动时的 JVM 参数

jstat:虚拟机统计信息监视工具

jstat(JVM Statistics Monitoring Tool)是用于监视虚拟机各种运行状态信息的命令行工具,在没有 GUI 图像界面、只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的常用工具。

它既可以连接本地虚拟机也可以连接远程虚拟机,如果连接远程虚拟机,那 VMID 的格式应当是:[protocol:][//]lvmid[@hostname[:port]/servername]

jstat 命令格式:jstat [option vmid [interval[s|ms] [count]]]

参数 interval 和 count 代表查询间隔和次数,如果省略这两个参数,说明只查询一次;下面的命令查询 vmid 为 2764 的 JVM 进程,且每 250 毫秒查询一次,一共查询 20 次。

bash 复制代码
$ jstat -gc 2764 250 20

可选项参数如下:

可选项 作用
-class 监视类加载、卸载数量、总空间以及类加载所耗费的时间
-gc 监视 Java 堆状况,包括 Eden 区、2 个 Survivor 区、老年代、永久代等的容量,已用空间,垃圾收集时间合计等信息
-gccapacity 监视内容与 -gc 基本相同,但输出主要关注 Java 堆各个区域使用到的最大、最小空间
-gcutil 监视内容与 -gc 基本相同,但输出主要关注已使用空间占总空间的百分比
-gccause 与 -gcutil 功能一样,但是会额外输出导致上一次垃圾收集产生的原因
-gcnew 监视新生代垃圾收集状况
-gcnewcapacity 监视内容与 -gcnew 基本相同,输出主要关注使用到的最大、最小空间
-gcold 监视老年代垃圾收集状况
-gcoldcapacity 监视内容与 -gcold 基本相同,输出主要关注使用到的最大、最小空间
-gcpermcapacity 输出永久代使用到的最大、最小空间
-compiler 输出即时编译器编译过的方法、耗时等信息
-printcompilation 输出已经被即时编译的方法

示例如下:

本地启动一个 Tomcat 服务,然后用 jps 命令获取 vmid 为 17260

bash 复制代码
$ jps -l
11232 org.jetbrains.idea.maven.server.RemoteMavenServer36
5940 org.jetbrains.jps.cmdline.Launcher
15784
16828 org.jetbrains.jps.cmdline.Launcher
17260 org.apache.catalina.startup.Bootstrap
5244 sun.tools.jps.Jps

使用 jstat 命令查看 JVM 内存使用情况和 gc 的统计信息。

输出列对应的含义如下:

列名 描述
S0C 年轻代中第一个 survivor 区的容量(字节)
S1C 年轻代中第二个 survivor 区的容量(字节)
S0U 年轻代中第一个 survivor 区目前已使用的容量(字节)
S1U 年轻代中第二个 survivor 区目前已使用的容量(字节)
EC 年轻代中 Eden 的容量(字节)
EU 年轻代中 Eden 目前已使用的容量(字节)
OC 老年代的容量(字节)
OU 老年代目前已使用的容量(字节)
MC 方法区大小(字节)
MU 方法区目前已使用的容量(字节)
CCSC 压缩类空间大小(字节)
CCSU 压缩类空间已使用大小(字节)
YGC 从应用程序启动到采样时年轻代中 minor gc 次数
YGCT 从应用程序启动到采样时年轻代中 minor gc 所用时间(s)
FGC 从应用程序启动到采样时 Full gc 的次数
FGCT 从应用程序启动到采样时 Full gc 所用时间(s)
GCT 从应用程序启动到采样时 gc 用的总时间(s)

jstat 命令展示的信息可知,启动 Tomcat 后进行了 3 次

jinfo:Java 配置信息工具

jinfo(Configuration Info for Java)的作用是实时查看和调整虚拟机各项参数。我们知道 JVM 在启动的时候有非常多的配置参数,比如堆大小、垃圾收集器等,在没有显示指定的情况下,这些参数有默认值,当我们向查看这个默认值是多少的时候,就可以使用 jinfo 命令,jinfo 命令还支持动态修改部分 JVM 参数。

jinfo 命令使用说明如下:

jinfo 命令可选项的含义:

可选项 含义
-flag /-flag [+|-] 打印指定 JVM 参数的值,也可以启用/禁用指定的 JVM 参数
-flags 打印 JVM 参数
-sysprops 打印 Java 系统属性

其中 pid 就是通过 jps 查询到的 vmid,两个的值是一样的。

bash 复制代码
# 打印 VM 参数
$ jinfo -flags 17260
Attaching to process ID 17260, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.311-b11
Non-default VM flags: -XX:CICompilerCount=12 -XX:InitialHeapSize=232783872 -XX:+ManagementServer -XX:MaxHeapSize=3722444800 -XX:MaxNewSize=1240465408 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=77594624 -XX:OldSize=155189248 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
Command line:  -Djava.util.logging.config.file=C:\Users\animal\AppData\Local\JetBrains\IntelliJIdea2020.1\tomcat\Unnamed_servlet-webapp_2\conf\logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:53422,suspend=y,server=n -javaagent:C:\Users\animal\AppData\Local\JetBrains\IntelliJIdea2020.1\captureAgent\debugger-agent.jar -Dcom.sun.management.jmxremote= -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.password.file=C:\Users\animal\AppData\Local\JetBrains\IntelliJIdea2020.1\tomcat\Unnamed_servlet-webapp_2\jmxremote.password -Dcom.sun.management.jmxremote.access.file=C:\Users\animal\AppData\Local\JetBrains\IntelliJIdea2020.1\tomcat\Unnamed_servlet-webapp_2\jmxremote.access -Djava.rmi.server.hostname=127.0.0.1 -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dignore.endorsed.dirs= -Dcatalina.base=C:\Users\animal\AppData\Local\JetBrains\IntelliJIdea2020.1\tomcat\Unnamed_servlet-webapp_2 -Dcatalina.home=D:\IT\tomcat\apache-tomcat-8.5.78-windows-x64\apache-tomcat-8.5.78 -Djava.io.tmpdir=D:\IT\tomcat\apache-tomcat-8.5.78-windows-x64\apache-tomcat-8.5.78\temp
​
# 查看最大堆内存参数的值
$ jinfo -flag MaxHeapSize 17260
-XX:MaxHeapSize=3722444800

jmap:Java 内存映像工具

jmap(Memory Map for Java)命令用于生成堆转储快照,除了 jmap 命令,还可以使用 -XX:HeapDumpOnOutOfMemoryError 参数让虚拟机在内存溢出异常出现之后自动生成堆转储快照文件。

jmap 命令的使用说明如下:

jmap 命令可选项的含义如下:

可选项 含义
-heap 打印 Java 堆详细信息,如使用哪种收集器、JVM 参数、分代状况等
-dump: 生成 Java 堆转储快照,dump-options 参数有 live:只 dump 存活的对象,如果没有指定,则 dump 出堆中的所有对象;format=b:二进制格式;file=:dump 到指定文件,示例:jmap -dump:live,format=b,file=heap.bin <pid>
-F 当虚拟机进程对 -dump 选项没有响应时,可使用这个选项强制生成 dump 快照,在这种模式下不支持 "live" 子选项

下面实战演示一下:

bash 复制代码
# 该选项的输出类似于 jinfo 命令,但是多了堆配置参数和垃圾收集器
$ jmap -heap 17260
Attaching to process ID 17260, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.311-b11
​
using thread-local object allocation.
Parallel GC with 13 thread(s)
​
Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 3722444800 (3550.0MB)
   NewSize                  = 77594624 (74.0MB)
   MaxNewSize               = 1240465408 (1183.0MB)
   OldSize                  = 155189248 (148.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)
​
Heap Usage:
PS Young Generation
Eden Space:
   capacity = 58720256 (56.0MB)
   used     = 58026680 (55.33855438232422MB)
   free     = 693576 (0.6614456176757812MB)
   98.81884711129325% used
From Space:
   capacity = 9437184 (9.0MB)
   used     = 0 (0.0MB)
   free     = 9437184 (9.0MB)
   0.0% used
To Space:
   capacity = 9437184 (9.0MB)
   used     = 0 (0.0MB)
   free     = 9437184 (9.0MB)
   0.0% used
PS Old Generation
   capacity = 109576192 (104.5MB)
   used     = 15419240 (14.704933166503906MB)
   free     = 94156952 (89.7950668334961MB)
   14.071706379429575% used
​
17183 interned Strings occupying 1607936 bytes.
​
# 生成堆转储快照文件
$ jmap -dump:file=heap.bin 17260
Dumping heap to D:\IT\heap.bin ...
Heap dump file created

执行 jmap -dump:file=heap.bin 17260 命令之后,可以看到在当前目录下生成了名为 heap.bin 的文件,它是一个二进制文件,用文本格式打开会显示乱码。

jhat:虚拟机堆转储快照分析工具

不管你是通过 -XX:HeapDumpOnOutOfMemoryError 参数还是通过 jmap 得到的堆转储快照文件,由于它是一个二进制文件,所以要分析它必须要另外的工具,jhat 就是用来分析堆转储快照的命令行工具。

jhat 内置了一个微型的 HTTP/Web 服务器,生成堆转储快照的分析结果后,可以在浏览器中查看。因为分析堆转储快照是一个耗时且耗费硬件资源的过程,因此不要再部署应用程序的服务器上直接分析堆转储快照文件,而是将其复制到其他服务器上再进行分析。

jhat 命令的使用说明如下:

使用 jhat 命令来分析之前用 jmap 得到的堆转储快照文件 heap.bin 文件。

bash 复制代码
$ jhat heap.bin
Reading from heap.bin...
Dump file created Wed Oct 04 12:09:18 CST 2023
Snapshot read, resolving...
Resolving 208424 objects...
Chasing references, expect 41 dots.........................................
Eliminating duplicate references.........................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

看到上面的输出结果说明分析成功,因为默认端口号是 7000(这一点也可以从 jhat 命令的输出中看出来),所以我们在浏览器中键入 http://127.0.0.1:7000 查看分析结果。页面如下:

分析结果默认以包为单位进行分组显示,分析内存泄露问题主要会使用到其中的 "Heap Histogram"(与 jmap -hsito 功能一样)与 OQL 页签的功能,前者可以找到内存中总容量最大的对象,后者是标准的对象查询语言,使用类似 SQL 的语法对内存中的对象进行查询统计。

jstack:Java 堆栈跟踪工具

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

jstack 命令的使用说明:

可选项的含义:

可选项 含义
-F 当正常输出的请求不被响应时,强制输出线程堆栈
-l 除堆栈信息外,显示关于锁的附加信息
-m 打印调用本地方法的堆栈信息
bash 复制代码
# jstack 会打印所有线程的堆栈信息,这里只展示了前三个线程
$ jstack -l 17260
2023-10-04 12:45:48
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.311-b11 mixed mode):
​
"http-nio-8080-AsyncTimeout" #47 daemon prio=5 os_prio=0 tid=0x00000111f42bc000 nid=0xa20 waiting on condition [0x0000000159efe000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at org.apache.coyote.AbstractProtocol$AsyncTimeout.run(AbstractProtocol.java:1256)
        at java.lang.Thread.run(Thread.java:748)
​
   Locked ownable synchronizers:
        - None
​
"http-nio-8080-Acceptor" #46 daemon prio=5 os_prio=0 tid=0x00000111f42b8000 nid=0x3910 runnable [0x0000000159dfe000]
   java.lang.Thread.State: RUNNABLE
        at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)
        at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:424)
        at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:252)
        - locked <0x00000006e283b9f8> (a java.lang.Object)
        at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:455)
        at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:71)
        at org.apache.tomcat.util.net.Acceptor.run(Acceptor.java:129)
        at java.lang.Thread.run(Thread.java:748)
​
   Locked ownable synchronizers:
        - None
​
"http-nio-8080-Poller" #45 daemon prio=5 os_prio=0 tid=0x00000111f42b9800 nid=0x230c runnable [0x0000000159cfe000]
   java.lang.Thread.State: RUNNABLE
        at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll0(Native Method)
        at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll(WindowsSelectorImpl.java:296)
        at sun.nio.ch.WindowsSelectorImpl$SubSelector.access$400(WindowsSelectorImpl.java:278)
        at sun.nio.ch.WindowsSelectorImpl.doSelect(WindowsSelectorImpl.java:159)
        at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
        - locked <0x00000006e2856638> (a sun.nio.ch.Util$3)
        - locked <0x00000006e2856648> (a java.util.Collections$UnmodifiableSet)
        - locked <0x00000006e28565b8> (a sun.nio.ch.WindowsSelectorImpl)
        at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
        at org.apache.tomcat.util.net.NioEndpoint$Poller.run(NioEndpoint.java:716)
        at java.lang.Thread.run(Thread.java:748)
​
   Locked ownable synchronizers:
        - None

从 JDK 5 开始,java.lang.Thread 类新增了一个 getAllStackTraces() 方法用于获取虚拟机中所有线程的 StackTraceElement 对象,使用这个方法可以通过简单的几行代码完成 jstack 的大部分功能,我们可以使用这个方法来做一个管理员页面,可以随时使用浏览器来查看线程堆栈。

可视化工具

JDK 中除了附带大量的命令行工具外,还提供了几个功能集成度更高的可视化工具,用户可以使用这些可视化工具以更加便捷的方式进行进程故障诊断和调试工作。这些工具主要包括 JConsole、VisualVM 和 JMC,其中 JConsole 在 JDK 5 中发布,VisualVM 在 JDK 6 中发布,Java Mission Control(JMC)在 JDK 7 中发布。

JConsole:Java 监视与管理控制台

JConsole(Java Monitoring and Management Console)是一款基于 JMX(Java Management Extensions)的可视化监视、管理工具。它的主要功能是通过 JMX 的 MBean(Managed Bean)对系统进行信息收集和参数动态调整。JMX 是一种开发性的技术,不仅可以用在虚拟机本身的管理上,还可以运行于虚拟机之上的软件中。

通过 JDK/bin 目录下的 jconsole.exe 启动 JConsole 后,会自动搜索出本机运行的所有虚拟机进程,而不需要用户自己使用 jps 来查询,如下图所示:

选择要监控的 JVM 进程,点击 "连接" 按钮进入 JConsole 主界面,主界面里共包括 "概述"、"内存"、"线程"、"类"、"VM 摘要" 和 "MBean" 六个页签。

页签的作用如下:

  • 概述:显示整个虚拟机主要运行数据的概览信息,包括 "堆内存使用情况"、"线程"、"类"、"CPU 使用情况" 四项信息的曲线图。
  • 内存:相当于可视化的 jstat 命令,用于监视被收集器管理的虚拟机内存的变化趋势。
  • 线程:相当于可视化的 jstack 命令,遇到线程停顿的时候可以使用这个页签的功能进行分析。
  • 类:显示虚拟机中类的统计信息
  • VM 摘要:当前连接的 VM 的摘要信息

在诊断 JVM 故障的时候我们最长使用 "内存" 和 "线程" 页签。

Java VisualVM:多合一故障处理工具

VisualVM(All-in-One Java Troubleshooting Tool)是功能最强大的运行监视和故障处理程序之一,曾经在很长一段时间内是 Oracle 官方主力发展的虚拟机故障处理工具。Oracle 曾在 VisualVM 的软件说明中写上了 "All-in-One" 的字样,预示着它除了常规的运行监视、故障处理外,还将提供其他方面的能力,例如性能分析。

VisualVM 基于 NetBeans 平台开发工具,所以它具备通过插件扩展功能的能力,有了插件扩展支持,VisualVM 可以做到:

  • 显示虚拟机进程以及进程的配置、环境信息(jps、jinfo)
  • 监控应用程序的处理器、垃圾收集、堆、方法区以及线程的信息(jstat、jstack)
  • dump 以及分析堆栈转储快照(jmap、jhat)
  • 方法级的程序运行性能分析,找出被调用最多、运行时间最长的方法
  • 离线程序快照:收集程序的运行时配置、线程 dump、内存 dump 等信息建立一个快照,可以将快照发送开发者处进行 Bug 反馈
  • 其他插件带来的无限可能性

初始状态下的 VisualVM 并没有加载任何插件,虽然基本的监视、线程面板的功能主程序都以默认插件的形式提供。

通过双击 %JAVA_HOME%/bin/jvisualvm.exe 来启动 VisualVM,主页面如下:

Java Mission Control:可支持在线的监控工具

和前面两款免费的可视化工具不同,JMC 如果要在生产环境使用需要商业授权才行,但是在个人开发环境下仍然是免费的。

需要注意的是,JDK 8 并不包含 JMC,你如果想要尝试 JMC 需要单独下载它,官方的下载地址是:jdk.java.net/jmc/,截至 2023-10-04,JMC 的最新版本是 JMC 8.3.1,从 JMC 8.1 开始运行 JMC 的 JDK 版本要求是 JDK 11 及以上,JMC 8.0 仍然仍然可以运行在 JDK 8 之上,所以你本地安装的是 JDK 8 的话,那么你需要下载 JMC 8.0 版本。

但是官网只提供了最新版本的下载连接,stackoverflow 上这个问题的答案列出了其他下载 JMC 发行版的地址,其中一个地址是:adoptium.net/zh-CN/jmc/,你可以在这个地址中下载到 JMC 8.0 版本。

解压之后的目录结构如下:

其中 jmc.imi 是配置文件,在启动 JMC 之前,你需要在 jmc.ini 文件中设置编码格式为 UTF-8,否则启动之后会出现乱码,在 jmc.ini 文件中添加一下内容即可:

conf 复制代码
-Dfile.encoding=UTF-8

最后双击 jmc.exe 启动 JMC。

在 "JVM 浏览器" 页签中展示了本机上正在运行的 JVM,双击其中一个进行连接,把连接后的进程展开可以看到每个进程的数据都有 MBean 和 JFR 两个数据来源。关于 MBean 这部分数据,与 JConsole 和 VisualVM 上取的内容是一样的,只是展示形式上有些差别。JFR 是和 MBean 不同的数据收集方式,它对被分析 JVM 程序性能影响和数据的质量都要比使用 MBean 要高。

相关推荐
weixin_SAG9 分钟前
21天掌握javaweb-->第19天:Spring Boot后端优化与部署
java·spring boot·后端
m0_7482475513 分钟前
SpringMVC跨域问题解决方案
java
Elcker14 分钟前
KOI技术-事件驱动编程(Sping后端)
java·spring·架构
GitNohup16 分钟前
Spring boot处理跨域问题
java·spring boot·跨域
Just_Paranoid27 分钟前
使用 IDE生成 Java Doc
java·开发语言·ide
西海天际蔚蓝43 分钟前
递归查询全量分页数据问题
java
SomeB1oody1 小时前
【Rust自学】7.4. use关键字 Pt.2 :重导入与换国内镜像源教程
开发语言·后端·rust
新知图书1 小时前
Rust编程与项目实战-箱
开发语言·后端·rust
俎树振1 小时前
深入理解与优化Java二维数组:从定义到性能提升的全面指南
java·算法
SomeB1oody1 小时前
【Rust自学】7.3. use关键字 Pt.1:use的使用与as关键字
开发语言·后端·rust