全面解析 JDK 提供的 JVM 诊断与故障处理工具
1. 引言:为什么需要 JVM 诊断工具?
在现代 Java 应用中,JVM(Java Virtual Machine)是承载所有 Java 程序运行的核心平台。它不仅负责类加载、内存分配、垃圾回收、线程调度等关键功能,还决定了程序的性能和稳定性。一旦 JVM 出现问题,如内存泄漏、线程死锁、GC 频繁、类加载失败等,将直接影响业务系统的可用性。
然而,JVM 是一个高度封装和自动化的黑盒系统,绝大多数运行时行为在默认情况下对开发者不可见。这就迫切需要一系列专业工具,帮助我们在运行时“窥视”JVM 的内部状态,进行排障和性能调优。
1.1 典型问题场景
以下是一些典型的问题场景,每一种都需要不同的工具协同解决:
内存泄漏 / 内存溢出(OutOfMemoryError):使用
jmap
导出堆快照,结合jhat
或 VisualVM 分析。频繁 Full GC 或 GC 时间过长:使用
jstat
、jcmd
或visualgc
观察 GC 行为,调优 JVM 参数。线程死锁 / 卡顿:使用
jstack
获取线程堆栈,结合代码定位死锁点。应用启动参数或环境不一致:使用
jinfo
查看运行时参数和系统属性。JVM 崩溃或 core dump:通过
hsdb
或jhsdb
分析 JVM 崩溃现场。
1.2 JVM 工具的重要性
JVM 诊断工具不仅在紧急问题排查中发挥关键作用,也在日常调优、回归验证、环境对比等方面具备重要价值。尤其是在大型微服务集群、多版本 JDK 混用、远程部署等复杂场景下,工具的可操作性和准确性成为保障系统稳定性的关键。
此外,随着 JDK 的不断演进(如 JDK 21+),部分传统工具逐步被 jcmd
统一整合,部分新功能(如 Flight Recorder、HeapDumpOnOutOfMemory)也提供了更强的观测能力。了解工具的历史沿革与现代替代方案,有助于在实际工作中选取最合适的方案。
1.3 本文目标与结构
本博客将系统介绍 JDK 提供的各类 JVM 诊断工具,重点聚焦如下几个方面:
每个工具的原理、用法、命令格式与示例
常见问题如何使用工具组合排查
JDK 21 及以后版本的工具变化与新增功能
工具的优劣分析及使用建议
希望通过这篇文章,帮助开发者掌握 JVM 运行时分析的核心技能,提升故障处理效率,构建更高质量、更稳定的 Java 应用系统。
2. jps:JVM 进程状态工具
2.1 工具简介
jps
(Java Virtual Machine Process Status Tool)是 JDK 提供的轻量级命令行工具,用于列出本地或远程主机上所有正在运行的 Java 进程及其相关信息。它的作用类似于操作系统的 ps
命令,但专门服务于 JVM 进程。
在使用如 jstack
、jmap
、jcmd
等高级工具前,首先要定位 Java 进程的 PID,而 jps
正是首选手段。
2.2 命令格式与参数说明
jps [options] [hostid]
常用参数:
-l
:输出主类的全名或 jar 文件完整路径-v
:输出传递给 JVM 的启动参数-m
:输出传递给 main 方法的参数-q
:仅显示进程 PID,不显示类名
示例:
jps -lv
2.3 使用示例与输出解析
示例一:列出所有本地 JVM 进程
$ jps
31584 Jps
31122 MySpringBootApp
30001 Kafka
29999 JConsole
第一列是 PID
第二列是主类名或 jar 包名
示例二:带参数查看类路径和 JVM 参数
$ jps -lv 31122 com.example.Main -Xms512m -Xmx1024m -Dspring.profiles.active=prod
示例三:仅列出 PID(适合脚本场景)
$ jps -q 31122 31584
2.4 典型用法示例
# 找到目标进程 PID 后,获取线程堆栈信息 jstack 31122 > thread_dump.txt
# 查看当前用户可见 JVM 列表,确认特定程序是否已启动 jps -l | grep MyApp
2.5 注意事项
jps
只能列出当前用户权限下可见的 JVM 进程,必要时加sudo
扩展权限某些精简版 JDK(如 Alpine Linux)可能缺少
jps
,需要安装完整 JDK如果提示
Could not find or load main class sun.tools.jps.Jps
,说明 JDK 安装不完整或环境变量配置有误
2.6 常见应用场景
快速确认 Java 应用是否在运行
多进程环境下快速获取目标进程 PID
与
jmap
、jstack
、jcmd
等工具协作使用结合脚本或监控工具自动化诊断
3. jstat:JVM 统计信息监控工具
3.1 工具用途及核心原理
jstat
(JVM Statistics Monitoring Tool)是 JDK 提供的用于实时监控 JVM 各种运行时统计信息的命令行工具。它可以帮助开发者分析 GC 行为、类装载、JIT 编译等运行状况,对于诊断内存问题和性能瓶颈至关重要。
核心原理基于 HotSpot 内部的性能计数器(Performance Counter),通过 VM attach 接口获取当前进程的状态信息,而不需额外侵入应用逻辑或重启服务。
3.2 命令格式与参数说明
jstat [option] <vmid> [interval] [count]
option
:监控类型(如gc
、class
等)vmid
:Java 进程 ID,可通过jps
获取interval
(可选):刷新间隔(毫秒)count
(可选):输出次数
常用 option 参数说明:
参数 | 说明 |
---|---|
class | 类装载、卸载数量 |
compiler | JIT 编译信息 |
gc | 垃圾回收概览统计 |
gccapacity | 每块内存区域容量信息 |
gcutil | 各内存区域使用率 |
printcompilation | 方法编译情况 |
3.3 使用示例与输出解析
示例一:查看 GC 活动
jstat -gc 31122 1000 5
每隔 1 秒采样一次,共输出 5 次,结果如:
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 1024.0 1024.0 0.0 8.5 8192.0 2560.0 32768.0 16384.0 4480.0 4212.3 512.0 487.6 5 0.045 1 0.032 0.077
字段解析:
S0C/S0U
:Survivor 0 区容量 / 使用量EC/EU
:Eden 区容量 / 使用量OC/OU
:Old 区容量 / 使用量YGC/YGCT
:年轻代 GC 次数 / 耗时FGC/FGCT
:Full GC 次数 / 耗时GCT
:GC 总耗时
示例二:类加载信息监控
jstat -class 31122 2000
输出:
Loaded Bytes Unloaded Bytes Time 5123 5.8M 12 150K 32.55
表示当前加载类数、卸载类数及耗时。
示例三:GC 使用率可视化数据
jstat -gcutil 31122 1000 3
输出类似:
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 12.00 0.00 32.35 50.67 94.44 78.91 7 0.121 2 0.046 0.167
可以看到各区使用百分比与 GC 次数、总耗时等。
3.4 注意事项
jstat
仅支持 HotSpot JVM,不支持 GraalVM。某些进程(特别是容器内部)需配置
-XX:+PerfDisableSharedMem
以防性能监控信息无法采集。不适用于挂死或核心 dump 的 JVM,可结合
jcmd
补充数据。
3.5 应用场景总结
分析 GC 活动是否频繁
查看老年代回收时间是否过长
监控类加载增长是否异常
用于自动化脚本、监控平台对接
4. jinfo:JVM 配置信息查询工具
4.1 工具介绍
jinfo
(Java Configuration Info Tool)是用于在运行时获取 Java 应用配置信息的工具,包括启动参数(如 -Xmx
)、系统属性(-D
选项)以及 JVM 运行时环境设置。
它适用于调试配置项是否生效、比对多环境 JVM 参数差异、验证线上服务启动参数等场景。
在某些平台上(如 Java 11+),jinfo
被逐步弱化,推荐使用 jcmd VM.flags
和 jcmd VM.system_properties
等子命令替代。
4.2 命令格式与参数说明
jinfo [option] <pid>
常用选项说明:
<pid>
:目标 Java 进程的 PID-flags
:显示 JVM 启动参数(包括默认、已显式设定和命令行)-sysprops
:显示系统属性(等价于System.getProperties()
)-flag [+|-]name
:动态开启或关闭布尔型参数(部分参数支持)
4.3 使用示例与输出说明
示例一:查看 JVM 启动参数
jinfo -flags 31122
输出示例:
-XX:InitialHeapSize=268435456
-XX:+PrintGC
-XX:MaxHeapSize=4294967296
其中:
=
后是对应的数值参数+
表示布尔参数启用,-
表示未启用
示例二:查看系统属性
jinfo -sysprops 31122
输出示例:
java.runtime.name=Java(TM) SE Runtime Environment
java.vm.version=25.281-b09
file.encoding=UTF-8
user.timezone=Asia/Shanghai
示例三:修改 JVM 参数(实验性)
某些参数可在运行时修改,例如:
jinfo -flag +PrintGC 31122
注意:此功能依赖 JVM 是否支持在线修改,很多商用 JVM 可能禁用。
4.4 注意事项
从 JDK 9 开始,
jinfo
在某些发行版中默认不再推荐使用,建议替代为jcmd
。不支持远程进程(需运行在本机或使用 SSH 登录目标服务器)。
某些参数即使修改成功,也不会立即生效,需重启应用。
4.5 应用场景总结
确认
-Xmx
、GC 选项、系统属性是否正确生效对比开发与生产环境参数差异
结合
jstat
分析配置与运行状态是否一致替代代码方式输出
System.getProperties()
的值
5. jmap:内存映射工具
5.1 工具用途与内部机制
jmap
(Java Memory Map)是 JDK 提供的用于查看和导出内存结构信息的命令行工具,主要用于:
查看对象在堆中的分布情况(如各类实例数量和内存占用)
导出堆快照(heap dump)文件,供后续分析工具使用(如 Eclipse MAT、VisualVM 等)
查看特定内存区域的使用信息
其原理是通过 SA(Serviceability Agent)
连接目标 JVM 的内存空间,提取堆内结构。部分模式依赖 HotSpot
内部接口,因此仅在特定 JVM 实现中可用。
5.2 命令格式与参数说明
jmap [option] <pid>
常用参数选项:
参数 | 说明 |
---|---|
-heap | 显示 Java 堆概况,包括新生代/老年代大小与使用率 |
-histo[:live] | 显示堆中对象的统计(类型、数量、大小),加 :live 可只统计可达对象 |
-dump:live,format=b,file=<path> | 导出堆快照文件,仅包含可达对象 |
-dump:format=b,file=<path> | 导出所有对象(包括不可达) |
-finalizerinfo | 显示 Finalizer 队列中待处理对象 |
提示:如在
jmap
输出中看到Unable to open socket file
错误,说明目标进程权限不足或未启用诊断接口。
5.3 使用示例与输出说明
示例一:查看堆使用概况
jmap -heap 31122
输出内容包括:
堆各区域(Eden、Survivor、Tenured)的总容量、使用大小
GC 相关策略(如 Parallel、CMS、G1)
GC 触发条件与堆增长策略
示例二:查看堆中对象的类型和数量
jmap -histo 31122 | head -n 20
输出示例:
num #instances #bytes class name
----------------------------------------------1: 53101 4673208 [C2: 12845 1953984 java.lang.String3: 23512 1880960 java.util.HashMap$Node
示例三:导出堆快照供分析工具使用
jmap -dump:live,format=b,file=/tmp/heapdump.hprof 31122
导出完成后,可使用如下工具打开:
Eclipse MAT(Memory Analyzer Tool)
VisualVM(带 Heap Dump 插件)
jhat(已弃用)
5.4 注意事项与限制
权限问题:必须对目标 JVM 拥有操作系统层的访问权限,建议使用与应用相同的用户或加 sudo。
性能影响:执行
-histo
或-dump
操作会Stop The World
,在生产环境中务必慎重。兼容性:部分参数在 JDK 9+ 后不再推荐,建议使用
jcmd GC.heap_info
和jcmd GC.class_histogram
作为替代。安全问题:某些容器环境(如 Docker)中需正确挂载
/proc
和 JVM 工具路径。
5.5 典型应用场景
内存泄漏定位:使用
-dump
导出堆并配合 MAT 查看GC Root
引用链。对象暴涨分析:通过
-histo
查看某类实例数量是否异常。对比启动后与运行中状态:定期采样、对比不同时间点堆的分布。
6. jhat:堆分析工具(已弃用)
7. jstack:线程堆栈分析工具
7.1 工具用途简介
jstack
是用于打印指定 Java 进程中所有线程的栈信息(Stack Trace)的工具。它常用于分析:
线程死锁(Deadlock)
阻塞或卡顿线程
热点线程状态(运行中、等待、睡眠)
垃圾回收线程活动
其原理是通过 JVM TI(Tool Interface)与目标进程交互,输出当前线程的状态及调用栈信息。
7.2 命令格式与参数说明
jstack [option] <pid>
常见参数:
参数 | 含义 |
---|---|
-F | 强制模式(用于无响应进程) |
-l | 输出扩展信息,如锁对象及栈上变量 |
-m | 显示 Java + 本地方法调用栈(混合模式) |
7.3 示例与输出分析
示例一:查看所有线程栈
jstack 31122 > thread_dump.txt
输出中每个线程包含如下信息:
"main" #1 prio=5 os_prio=0 tid=0x0000000001d0f000 nid=0x7ac runnable [0x00000000029af000]java.lang.Thread.State: RUNNABLEat com.example.Test.main(Test.java:15)
字段说明:
线程名称(如 "main")
tid/nid:JVM 和操作系统线程 ID
状态:
RUNNABLE
、WAITING
、TIMED_WAITING
、BLOCKED
等堆栈信息:调用路径,显示阻塞点
示例二:检测死锁
jstack -l 31122 | grep -A 20 "Found one Java-level deadlock"
若存在死锁,会出现如下内容:
Found one Java-level deadlock:
"Thread-1": waiting to lock monitor 0x00007fae9c0030a0 (object 0x000000076b0f2a78), which is held by "Thread-2"
"Thread-2": waiting to lock monitor 0x00007fae9c0030b8 (object 0x000000076b0f2a90), which is held by "Thread-1"
7.4 示例程序:模拟死锁进行分析
public class DeadlockDemo {private static final Object lockA = new Object();private static final Object lockB = new Object();public static void main(String[] args) {Thread t1 = new Thread(() -> {synchronized (lockA) {try { Thread.sleep(100); } catch (Exception ignored) {}synchronized (lockB) {System.out.println("Thread-1 完成");}}});Thread t2 = new Thread(() -> {synchronized (lockB) {try { Thread.sleep(100); } catch (Exception ignored) {}synchronized (lockA) {System.out.println("Thread-2 完成");}}});t1.start();t2.start();}
}
执行上述程序后使用 jstack
查看,会检测到死锁信息。
7.5 注意事项与实践建议
jstack
在 Linux 上必须使用与目标 JVM 相同的用户或 root 权限若目标进程无响应,使用
-F
强制输出多次采样(如每 10 秒输出一次)可更有效观察线程状态变化
可结合
top -Hp <pid>
定位 CPU 占用高的线程,通过nid
对应jstack
中的线程信息
8. jcmd:现代通用诊断命令工具
8.1 工具概览与设计理念
jcmd
(Java Command)是 Java 7 之后引入的统一诊断接口工具,旨在替代传统的 jmap
、jstack
、jinfo
等分散工具。通过向目标 JVM 发送特定命令,实现对内存、线程、GC、类加载等状态的实时观测和控制。
它具备以下特点:
命令集合统一(如
GC.heap_info
、Thread.print
、VM.flags
)输出信息更丰富,格式更标准
支持 JFR(Java Flight Recorder)等高级功能
支持自定义命令扩展(从 JDK 11 起)
8.2 命令格式与基本用法
jcmd <pid | main-class> <command> [options]
如:
jcmd 31122 GC.heap_info
查看支持的命令列表:
jcmd 31122 help
输出示例:
31122:VM.versionGC.class_histogramGC.heap_infoThread.printVM.system_properties
8.3 常用子命令与示例
1. 查看堆信息(替代 jmap -heap)
jcmd 31122 GC.heap_info
输出包括:
堆各区域的使用情况(Eden、Old、Survivor)
GC 实现名称(G1、ZGC、Shenandoah 等)
垃圾收集策略与容量限制
2. 获取堆中对象统计信息(替代 jmap -histo)
jcmd 31122 GC.class_histogram
类似 jmap -histo
,输出实例数、占用空间、类名等信息:
num #instances #bytes class name
----------------------------------------------1: 53101 4673208 [C2: 12845 1953984 java.lang.String
3. 打印所有线程的栈信息(替代 jstack)
jcmd 31122 Thread.print
4. 查看或修改 JVM 启动参数(替代 jinfo -flags)
jcmd 31122 VM.flags
5. 查看系统属性(等价于 System.getProperties)
jcmd 31122 VM.system_properties
6. 导出堆 Dump 文件(替代 jmap -dump)
jcmd 31122 GC.heap_dump /tmp/heapdump_31122.hprof
8.4 JDK 21+ 新增或推荐命令
在 JDK 21 中,jcmd
引入或推荐以下新命令:
JFR.configure
/JFR.start
/JFR.stop
:管理 JFR 事件记录VM.native_memory summary
:内存占用快照(Native Memory Tracking)Compiler.codecache
:查看 JIT 编译缓存GC.finalizer_info
:输出 Finalizer 队列状态
8.5 注意事项与最佳实践
推荐使用
jcmd help
获取当前进程可支持的完整命令列表与传统工具相比,
jcmd
提供的信息更全面且格式标准某些命令如
GC.class_stats
需开启-XX:+UnlockDiagnosticVMOptions
在容器中使用时应确保 PID 与宿主机一致或使用
docker exec
进入容器内执行
8.6 应用场景总结
诊断任务 | 推荐 jcmd 命令 |
---|---|
堆内存使用 | GC.heap_info |
对象实例统计 | GC.class_histogram |
导出堆快照 | GC.heap_dump |
打印线程栈 | Thread.print |
查看参数 | VM.flags |
查看系统属性 | VM.system_properties |