摘要JVM 调优的第一步是看得见——jstat 看内存趋势、jmap 做堆dump、jstack 抓线程快照、jcmd 统一诊断……这些 JDK 内置工具是每个 Java 工程师必须掌握的基本功。本文从实战角度讲解jstat/jmap/jstack/jcmd/VisualVM五大工具的核心用法如何用 jstat 发现内存泄漏的苗头、如何用 jmap MAT 分析 OOM 根因、如何用 jstack 定位死锁和 CPU 100% 问题、如何用 jcmd 一站式诊断。同时介绍Async-profiler和Arthas两个高级诊断工具让你的问题定位效率提升 10 倍。引言“线上 CPU 飙到 100% 了怎么办”“内存一直增长什么时候会 OOM”“接口超时怎么定位是 GC 导致的”这些问题都指向同一个能力JVM 运行时监控与诊断。JDK 自带了一系列命令行工具99% 的线上问题都可以用它们定位。问题是大多数工程师只会jps查看进程 ID。JVM 诊断工具矩阵 ┌─────────────────────────────────────────────────────────────────┐ │ JDK 内置诊断工具 │ ├────────────┬────────────────────────────────────────────────────┤ │ jps │ 查看 Java 进程 ID 和启动参数 │ │ jstat │ GC 统计、内存使用、类加载、编译信息 │ │ jmap │ 堆转储、内存分布、对象统计 │ │ jstack │ 线程快照、死锁检测、CPU 热点 │ │ jcmd │ 综合诊断工具JDK 8统一 jmap/jstack 等 │ │ jinfo │ 运行时查看/修改 JVM 参数 │ ├────────────┴────────────────────────────────────────────────────┤ │ 高级诊断工具 │ ├────────────┬────────────────────────────────────────────────────┤ │ VisualVM │ 图形化 JVM 监控工具支持插件扩展 │ │ async-profiler | 低开销采样分析器支持 async-profiler │ │ Arthas │ 阿里开源在线诊断、热修复、方法追踪 │ │ Java Flight Recorder | 商业级录制工具OpenJDK 可用 │ └─────────────────────────────────────────────────────────────────┘一、jps进程查看器1.1 基本用法# 查看所有 Java 进程jps-l# 输出# 12345 com.example.Application# 12346 jar target/myapp.jar# 12347 sun.tools.jps.Jps# 查看详细启动参数jps-lv# 输出# 12345 com.example.Application -Xms4g -Xmx4g -XX:UseG1GC ...# 查看传递给 main 方法的参数jps-m1.2 实战场景# 快速定位 Java 进程jps|grepmyapp# 结合 ps 查找psaux|grepjava|grep-vgrep# 查看所有 Java 相关进程包括子进程jps-m-l-v二、jstatGC 统计神器2.1 核心选项# 语法jstat -optionpid[interval[count]]# 常用选项-gcutil# GC 覆盖率百分比最常用-gc# 各区域容量和使用量字节-gccapacity# 各区域容量不显示使用量-gcnew# 年轻代 GC 统计-gcold# 老年代 GC 统计-gcmetaspace# 元空间统计-class# 类加载统计-compiler# JIT 编译统计-printcompilation# 最近编译的方法2.2 GC 覆盖率最常用# 每秒输出一次 GC 覆盖率共输出 10 次jstat-gcutil12345100010# 输出# S0 S1 E O M YGC YGCT FGC FGCT GCT# 12.50 0.00 65.00 78.50 85.20 123 4.567 12 0.890 5.457# 字段含义# S0/S1: Survivor 0/1 使用率%# E: Eden 区使用率%# O: Old 区使用率%# M: Metaspace 使用率%# YGC: 年轻代 GC 总次数# YGCT: 年轻代 GC 总耗时秒# FGC: Full GC 总次数# FGCT: Full GC 总耗时秒# GCT: GC 总耗时秒┌──────────────────────────────────────────────────────────────────┐ │ jstat -gcutil 核心指标速读 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 正常模式锯齿波动 │ │ S0: 0% ───┐ │ │ E : 0% ──┤──→ 突然飙升 ──→ GC 后回落 │ │ O : 70% ─┘ O 区稳定在 70% 是健康的 │ │ │ │ 危险模式持续上升 │ │ O : 70% → 75% → 80% → 85% → ... │ │ ↑ 这是内存泄漏的早期信号 │ │ │ │ 告警阈值建议 │ │ - O 80% 持续 5 分钟 → 关注可能需要调优 │ │ - O 90% → 接近 OOM应立即介入 │ │ - M 85% → 元空间增长关注是否有大量类加载 │ │ │ └──────────────────────────────────────────────────────────────────┘2.3 内存详细统计# 查看各区域详细容量和字节数jstat-gc12345# 输出# S0C S1C S0U S1U EC EU OC OU# 349696.0 349696.0 43687.3 0.0 2797568.0 1832465.0 6997632.0 5497632.0# MC MU CCSC CCSU YGC YGCT FGC FGCT GCT# 5120.0 4352.0 768.0 640.0 123 4.567 12 0.890 5.457# 字段含义# S0C/S1C: Survivor 0/1 容量KB# S0U/S1U: Survivor 0/1 已使用KB# EC/EU: Eden 容量/已使用KB# OC/OU: Old 容量/已使用KB# MC/MU: Metaspace 容量/已使用KB2.4 实战监控内存趋势# 监控脚本检测内存泄漏#!/bin/bashPID$1INTERVAL5THRESHOLD90whiletrue;doOLD_USAGE$(jstat-gcutil$PID|tail-1|awk{print $5})echo$(date%Y-%m-%d %H:%M:%S)Old:${OLD_USAGE}%if(($(echo $OLD_USAGE$THRESHOLD|bc-l)));thenecho⚠️ WARNING: Old generation usage ${THRESHOLD}%# 触发堆转储jmap-dump:formatb,fileheap_${PID}_$(date%s).hprof$PIDfisleep$INTERVALdone三、jmap堆内存诊断3.1 堆直方图快速定位# 查看堆中对象统计按包名聚合jmap-histo12345|head-50# 输出# num #instances #bytes class name (module)# -------------------------------------------------------# 1: 12345 52428800 [Ljava.lang.Object;# 2: 8901 12345678 com.example.User# 3: 5678 9876543 com.example.Order# 4: 3456 2345678 java.lang.String# 5: 2345 1234567 java.util.HashMap$Node# 查找可疑的大对象jmap-histo12345|awk$3 10000000 {print}# 找出占用超过 10MB 的对象类型# 查看类加载器统计jmap-clstats123453.2 堆转储核心技能# 生成堆转储文件生产环境必用# JDK 8 格式jmap-dump:formatb,fileheapdump.hprof12345# JDK 11 推荐live 对象只 dump 活着的对象更小更快jmap -dump:live,formatb,fileheapdump_live.hprof12345# JDK 11使用 jcmd更推荐jcmd12345GC.heap_dump heapdump.hprof jcmd12345GC.heap_dump-livetrue heapdump_live.hprof# ⚠️ 注意堆转储会 Stop-The-World大堆8GB可能暂停 30 秒以上# 建议在低峰期执行或使用 -XX:HeapDumpOnOutOfMemoryError 自动触发3.3 堆内存分布# 查看堆内存区域详细信息jmap-heap12345# 输出# Heap Configuration:# MaxHeapSize 4294967296 (4.0GB)# NewSize 1073741824 (1.0GB)# MaxNewSize 1073741824 (1.0GB)# OldSize 3221225472 (3.0GB)# NewRatio 3# SurvivorRatio 8# MetaspaceSize 268435456 (256.0MB)# ...## Heap Usage:# PS Young Generation# Eden Space:# capacity 8589934592 (8.0GB)# used 1234567890 (1.1GB)# free 7355366602 (6.9GB)# 14.37% used# ...四、jstack线程诊断4.1 线程快照# 生成线程快照jstack12345threaddump.txt# 查找死锁jstack-l12345# 输出中的关键部分# Found one Java-level deadlock:# # Thread-A:# waiting for ownable synchronizer 0x00007f1234567890,# (a java.util.concurrent.locks.ReentrantLock$NonfairSync)# which is held by Thread-B# Thread-B:# waiting for ownable synchronizer 0x00007f12345678a0,# (a java.util.concurrent.locks.ReentrantLock$NonfairSync)# which is held by Thread-A4.2 CPU 100% 定位# 步骤 1找到占用 CPU 最高的线程top-Hp12345# 输出 PID 列表找到占用 CPU 最高的线程 PID# 步骤 2转换为十六进制printf%x\n12346# 假设输出3032# 步骤 3从 jstack 输出中查找该线程jstack12345|grep-A503032# 输出示例# pool-1-thread-10 #12345 prio5 os_prio0 tid0x00007f1234567890# java.lang.Thread.State: RUNNABLE# at com.example.Service.processLoop(Service.java:45)# at com.example.Service.run(Service.java:123)# at java.lang.Thread.run(Thread.java:750)# ↑# 根因代码位置4.3 线程状态分析jstack 线程状态速查 ┌──────────────────────────────────────────────────────────────────┐ │ 状态 │ 含义 │ ├─────────────────────┼──────────────────────────────────────────┤ │ RUNNABLE │ 正在运行可能在计算也可能在等待 I/O │ │ BLOCKED │ 被阻塞等待获取锁 │ │ WAITING │ 无限等待Object.wait, LockSupport.park │ │ TIMED_WAITING │ 限时等待Thread.sleep, Lock.wait │ │ NEW / TERMINATED │ 未启动 / 已结束 │ ├──────────────────────────────────────────────────────────────────┤ │ 危险信号 │ │ - 大量 RUNNABLE 且 CPU 高 → 死循环、密集计算 │ │ - 大量 BLOCKED → 锁竞争激烈 │ │ - 大量 WAITING → 线程池配置不当或任务饥饿 │ └──────────────────────────────────────────────────────────────────┘五、jcmd统一诊断工具JDK 8 引入的 jcmd 是诊断工具的瑞士军刀# 查看可用命令jcmd12345help# 常用命令jcmd12345GC.heap_dump /tmp/heap.hprof# 堆转储jcmd12345GC.run# 手动触发 Full GCjcmd12345GC.class_histogram# 堆直方图jcmd12345GC.class_stats# 类元数据统计JDK 8u45jcmd12345VM.flags# 查看所有 JVM 参数jcmd12345VM.system_properties# 查看系统属性jcmd12345VM.version# JVM 版本信息jcmd12345Thread.print# 等同于 jstackjcmd12345VM.native_memory# 本地内存跟踪六、VisualVM图形化监控6.1 核心功能# 启动 VisualVMjvisualvm# 或命令行指定端口连接远程 JVMjvisualvm--openjmxlocalhost:9999VisualVM 功能模块 ┌─────────────────────────────────────────────────────────────────┐ │ VisualVM 主界面 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 【监控】标签 │ │ - 堆使用量折线图实时 │ │ - Metaspace 使用量 │ │ - 线程数 │ │ - 类加载数量 │ │ - CPU 使用率 │ │ │ │ 【线程】标签 │ │ - 线程状态时序图 │ │ - 死锁检测 │ │ - 线程堆栈查看 │ │ │ │ 【抽样器】标签 │ │ - CPU 采样找出热点方法 │ │ - 内存采样找出内存消耗大户 │ │ │ │ 【Profiler】标签需要安装插件 │ │ - 更精确的性能分析 │ │ - 内存分配追踪 │ │ │ └─────────────────────────────────────────────────────────────────┘七、高级工具推荐7.1 Async-profiler推荐低开销的采样分析器比 JProfiler 更轻量# 安装wgethttps://github.com/async-profiler/async-profiler/releases/download/v2.9/async-profiler-2.9-linux-x64.tar.gztar-xzfasync-profiler-2.9-linux-x64.tar.gz# CPU 采样30 秒./profiler.sh-d30-fcpu.htmlpid# 内存分配采样./profiler.sh-d30-ealloc-falloc.htmlpid# 锁竞争分析./profiler.sh-d30-elock-flock.htmlpid7.2 Arthas阿里开源在线诊断神器可以在不重启应用的情况下诊断问题# 启动java-jararthas-boot.jar# 常用命令dashboard# 查看 JVM 整体状态仪表盘thread# 查看线程状态thread-n3# 查看 CPU 最高的 3 个线程trace com.example.* run*#cost 100# 追踪方法调用耗时watchcom.example.UserService getUser{params, returnObj}-x3monitor-c5com.example.* hello# 每 5 秒统计方法调用jad com.example.ClassName# 在线反编译总结JVM 诊断工具是调优的眼睛。核心工具链jstat -gcutil每秒监控 GC 趋势发现内存泄漏苗头jstack抓线程快照定位死锁和 CPU 100%jmap -dump生成堆转储用 MAT 分析 OOM 根因Arthas/async-profiler在线诊断追踪方法耗时和内存分配系列导航上一篇【JVM深度解析】第11篇GC日志配置与可视化分析下一篇【JVM深度解析】第13篇生产环境JVM配置最佳实践系列目录JVM深度解析系列全集参考资料Oracle JDK Tools ReferenceVisualVM DocumentationAsync-profiler GitHubArthas DocumentationMAT - Memory Analyzer Tool