JDK 自带工具:jstat /jstack/jmap
前提:
这些命令属于JDK bin,不是 JRE;
只能抓本地 Java 进程,需要知道进程 PID,查看 PID 命令:
jps -l(最常用)
jps=Java Virtual Machine Process Status Tool,JDK 自带工具,专门查看 Java 进程,不需要额外安装。
-l是 long(长格式)作用:输出PID + 主类全限定名 /jar 完整路径
# 查看java进程,拿到pid jps -l假设示例中 Java 进程 PID =12345
一、jstat:JVM 运行状态统计工具(看 GC、类加载、编译等指标)
全称:JVM Statistics Monitoring Tool,JDK 自带,
实时采样监控 JVM 运行统计,最常用就是看 GC 情况,
jstat [-options] <pid> [间隔ms] [采样次数]常用 option
gc | GC 统计(最常用) |
gccapacity | 堆各区域容量 + 当前占用 |
gcutil | GC 百分比视图(推荐,一眼看使用率) |
gccause | gcutil + 最后一次 GC 原因 |
class | 类加载统计 |
compiler | JIT 编译统计 |
场景 1:排查频繁 FullGC,观察 GC 指标(最常用)
需求:每 1000ms 输出一次,一共输出 10 次,查看 S0、S1、E、O、M 的GC 占比
jstat -gcutil 12345 1000 10示例输出:
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 89.23 45.12 62.31 94.11 91.23 2145 32.123 12 2.456 34.579字段解释:
- S0/S1:Survivor0、Survivor1 使用率
- E:Eden 区使用率
- O:老年代 Old 使用率
- M:元空间 Metaspace 使用率
- YGC:年轻代 GC 次数;YGCT:YGC 总耗时
- FGC:FullGC 次数;FGCT:FullGC 总耗时
- GCT:GC 总耗时
✅ 判断点:
- O 老年代持续上涨,不断触发 FGC →内存泄漏
- FGC 次数持续飙升,FGCT 时间变长 → 严重卡顿
场景 2:查看堆内存各区域容量
jstat -gccapacity 12345 1000 5可以看到 Eden、Survivor、Old 的最大容量、当前容量。
场景 3:查看类加载情况(排查类泄漏、元空间满)
jstat -class 12345输出:加载类数量、卸载数量、耗时。如果类持续不断加载不卸载 → 元空间溢出。
二、jstack:线程栈分析工具(死锁、线程阻塞、CPU 高、线程池打满)
Java Stack Trace,JDK 自带工具,打印 JVM 内所有线程栈快照
作用:
- Java 死锁
- CPU100%(线程无限循环)
- 线程阻塞、等待锁
- 线程池耗尽、接口长时间 hang 住
语法:
jstack [-l] <pid>-l:额外输出锁信息(long listing),排查死锁必加
⚠️ 如果进程卡死,可以加
-F强制 dump(jstack -F 12345),但 - F 抓取信息会丢失部分信息,优先不用。
✅一个进程里面包含多个线程。进程是资源容器,线程是 CPU 调度的最小单位。
场景 1:线上 CPU 飙升,定位哪段代码占用 CPU
完整排查流程:
top找到java进程PID=12345top -H -p 12345查看进程内线程,找到 CPU 最高线程号,假设线程号23456(十进制)jstack 12345 > thread.log导出线程栈日志- 十进制转十六进制:
printf "%x\n" 23456→ 0x5ba0=nid - 在 thread.log 搜索
nid=0x5ba0,找到对应线程栈,定位代码行
-p 12345:只监控 PID=12345 这个进程-H:开启线程模式,把进程内部所有线程展示出来(top 默认只展示进程,不展开线程)
场景 2:排查死锁
jstack -l 12345 > deadlock.logjstack 会自动检测死锁,日志末尾会输出Found one Java-level deadlock,打印两个线程互相等待的锁和代码位置。
示例死锁片段:
Found one Java-level deadlock: ============================= "Thread-A": waiting to lock monitor 0x00007f8b1c003210 (object 0x000000076ac21498, a java.lang.Object), which is held by "Thread-B" "Thread-B": waiting to lock monitor 0x00007f8b1c003320 (object 0x000000076ac214a8, a java.lang.Object), which is held by "Thread-A"场景 3:接口响应缓慢,大量线程 WAITING/BLOCKED
jstack 12345 > stack.log看线程状态:
BLOCKED:被锁阻塞WAITING:等待唤醒(lock.wait () / 线程池队列等待)TIMED_WAITING:限时等待(sleep)RUNNABLE:运行中(IO 阻塞也会显示 RUNNABLE)
常见坑:大量线程卡在数据库连接池等待,就是大量 WAITING。
三、jmap:内存映像工具,堆内存分析(OOM、内存泄漏)
全称:Java Memory Map,JDK 自带堆内存分析工具。
核心能力:查看堆配置、查看当前堆使用情况、dump 堆快照文件,
用于分析OOM、内存泄漏。
语法:
jmap [option] <pid>常用 option
jmap -heap <pid>:打印堆配置与当前堆概览(快速看堆参数)jmap -histo[:live] <pid>:查看堆内对象统计(对象数量、占用大小)histo:所有对象;histo:live只统计存活对象(会触发 FullGC)
jmap -dump:format=b,file=heap.hprof <pid>:导出 hprof 堆快照文件(核心!)
⚠️ dump 堆文件会暂停应用,生产尽量业务低峰操作;hprof 文件建议下载本地,用 MAT(Memory Analyzer Tool)分析。
场景 1:快速查看 JVM 堆配置(Xmx/Xms/ 新生代比例)
jmap -heap 12345输出:堆最大最小,Eden、Survivor、Old 大小,垃圾收集器类型。
场景 2:查看堆中对象数量,初步判断内存泄漏
jmap -histo:live 12345 > histo.log输出按占用内存排序:
num instances bytes class name 1 1200000 48000000 java.util.ArrayList 2 800000 32000000 com.xxx.entity.User如果自定义实体类数量持续暴涨,大概率内存泄漏。
场景 3:OOM 发生,导出堆快照(最常用)
jmap -dump:format=b,file=heap_20261008.hprof 12345拿到heap_20261008.hprof文件,下载到本地,用 MAT 工具打开:
- 看大对象
- 看对象引用链,找出是谁持有对象没有释放
推荐:JVM 启动参数增加
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/xxx/,OOM 时自动 dump 堆文件,不用手动执行 jmap。
不推荐场景:生产不要频繁执行jmap -histo:live
因为加了:live会触发 FullGC,线上业务会卡顿。
四、三个工具对比总结
| 命令 | 关注点 | 适用场景 |
|---|---|---|
| jstat | GC 指标、类加载、JIT | 持续监控 GC,判断是否有 GC 异常 |
| jstack | 线程栈、锁、线程状态 | CPU 高、死锁、接口 hang、线程阻塞 |
| jmap | 堆内存、对象、堆 dump | OOM、内存泄漏,分析对象占用 |
五、一套完整线上故障排查流程示例
现象:服务接口变慢,时不时 OOM,老年代持续上涨
jps -l获取 pidjstat -gcutil pid 1000观察,发现 Old 不断上涨,FGC 越来越多jstack pid查看是否有线程泄漏、连接不释放jmap -histo pid看到自定义业务对象数量持续暴涨jmap -dump:format=b,file=heap.hprof pid导出堆文件- MAT 分析 hprof,找到对象引用链定位代码泄漏点
六、扩展
Q1:jstat -gcutil 各个字段含义?FGC 持续上涨代表什么?
答:
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT- S0:Survivor0 使用率;S1:Survivor1 使用率
- E:Eden 区使用率;O:老年代使用率
- M:元空间 Metaspace 使用率;CCS:压缩类空间使用率
- YGC:年轻代 GC 次数;YGCT:YGC 总耗时
- FGC:FullGC 次数;FGCT:FullGC 总耗时
- GCT:GC 总耗时
FGC 持续上涨分两种情况:
- 每次 FGC 后 Old 能明显下降:正常业务,短期大对象,可观察;
- 每次 FGC 后 Old 下降很少,很快又涨满:内存泄漏,对象无法回收,需要 jmap dump 堆分析。
面试加分:FGC 非常耗时间,会 STW,频繁 FGC 会造成接口超时、服务卡顿。
Q2:jstack 怎么排查 CPU100%?完整步骤
答:
top找到 Java 进程 PID;top -H -p pid查看进程内所有线程,找到 CPU 最高的十进制线程 id;printf "%x\n 线程id"转为十六进制 nid;jstack pid > thread.log导出线程栈;- 在日志搜索
nid=0x十六进制值,定位到对应线程栈,找到阻塞 / 死循环代码。
注意:jstack 输出里的 nid 是十六进制,top 看到的线程号是十进制,很多人这里踩坑。
Q3:jstack 看到大量 BLOCKED、WAITING 分别是什么含义?
答:
BLOCKED:线程正在等待监视器锁(synchronized),拿不到锁被阻塞;大概率锁竞争激烈、死锁;WAITING:无限等待,等待被其他线程唤醒,如Object.wait()、LockSupport.park();常见:线程池无任务、数据库连接池拿不到连接;TIMED_WAITING:限时等待,Thread.sleep()、wait(time);RUNNABLE:运行中;注意:网络 IO 阻塞时,线程状态依然是 RUNNABLE,这点很容易答错。
Q4:jmap -histo 和 jmap -histo:live 的区别?生产慎用哪个?为什么?
答:
jmap -histo pid:打印堆中所有对象统计,不触发 FullGC;jmap -histo:live pid:只统计存活对象,执行前会触发一次 FullGC。
生产慎用:
histo:live,因为 FullGC 会 STW,业务请求卡顿,高峰期可能雪崩。
Q5:jmap dump 堆快照有什么风险?生产什么时候 dump?
答:风险:
- dump 过程会STW(暂停应用);
- hprof 文件大小接近堆最大值,占用大量磁盘 IO;
- 堆越大,dump 耗时越长。
最佳实践:
- 低峰期 dump;
- 优先配置 JVM 参数:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp,OOM 时自动 dump,不需要人工执行 jmap; - 拿到 hprof 下载到本地,用 MAT 分析,不要在线上直接分析。
Q6:jstat、jstack、jmap 三者分工,故障排查时使用顺序?
答:
- jstat:宏观监控,看 GC 指标,先判断有没有内存 / GC 问题;
- jstack:线程层面,查 CPU 高、死锁、接口 hang;
- jmap:内存对象层面,OOM、内存泄漏,dump 堆快照。
排查顺序:jps→jstat(确认 GC 异常)→jstack(看线程是否阻塞)→jmap(导出堆,定位泄漏对象)
Q7:jstack -F 参数作用?缺点?
答:jstack pid正常抓取;当进程卡死,正常 jstack 无法响应时,使用jstack -F pid强制 dump。 缺点:强制抓取会丢失部分信息,丢失 Java 锁信息,优先不用。
Q8:容器环境使用这些命令有什么坑?
答:容器内 Java 进程 PID 是容器内 PID,宿主机看不到; 解决:启动容器添加--pid=host,共享宿主机 pid 命名空间; 另外注意:JDK 镜像,必须装 JDK,不能只用 JRE(缺少 jstat/jstack/jmap 工具)。
Q9:OOM 一定是内存泄漏吗?
答:不一定。
- 内存泄漏:对象不再使用,但是一直被引用,GC 无法回收,Old 持续上涨;
- 非泄漏 OOM:一次性加载超大文件 / 大查询,瞬间分配对象超过堆上限;堆配置太小。
可以通过 jstat 观察区分:泄漏是缓慢持续涨老年代;大对象是瞬间把 Old 打满。
Q10:jstat 可以看线程堆栈吗?jstack 能看 GC 指标吗?
答:都不行。 jstat:只输出统计指标,看不到代码堆栈; jstack:只打印线程栈,不统计 GC、内存占用; 各司其职。