前言
jstat(Java Virtual Machine Statistics Monitoring Tool)是 JDK 自带的一个轻量级命令行工具,用于监控 JVM 的运行状态,特别是 垃圾回收(GC) 和 内存使用情况。它通过连接目标 JVM 的 Performance Data(PerfData)机制,实时读取 JVM 内部的性能计数器,无需侵入应用代码,对性能影响极小,因此非常适合在生产环境中使用。
一、jstat 的工作原理
1. 基于 PerfData 机制
- JVM 启动时会创建一个共享内存区域(通常位于
/tmp/hsperfdata_<user>/<pid>),其中包含各种性能指标(如堆内存各代大小、GC 次数、耗时等)。 jstat通过读取该共享内存中的数据,实时展示 JVM 的运行状态。- 不依赖 JMX 或远程调试,开销极低。
2. 监控维度
jstat 支持多种选项,常用包括:
gc:显示 GC 相关统计(Eden、Survivor、Old、Metaspace 等区的容量和使用量)gcutil:以百分比形式显示各代内存使用率(最常用)gccause:显示 GC 原因(如 Allocation Failure、System.gc() 等)compiler/class:监控 JIT 编译或类加载情况(较少用于 OOM 排查)
二、排查使用案例
步骤 1:定位目标进程 PID
ps -ef | grep your-app-name
# 或
jps -v
假设 PID 为 12345。
步骤 2:使用 jstat 实时监控 GC 和内存使用
# 每 2 秒输出一次,共 10 次
jstat -gcutil 1234 -h10 2s
输出示例:
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 98.50 100.00 85.20 96.30 92.10 120 2.450 5 8.200 10.650
0.00 98.50 100.00 85.20 96.30 92.10 120 2.450 5 8.200 10.650
...
关键字段解读:
- E (Eden) :持续 100% 且不下降 → Young GC 无法释放对象,可能大量短生命周期对象晋升到 Old 区。
- O (Old) :持续增长(如从 70% → 85% → 95%)→ 内存泄漏典型特征。
- YGC / FGC:Young GC 频繁但 Old 区仍在涨;Full GC 次数少但 Old 区不降 → 对象无法回收。
- FGCT(Full GC 耗时) :若单次 Full GC 超过 1 秒,说明堆很大或存在大量不可回收对象。
或:
jstat -gc <pid> [interval] [count]
jstat -gc 12345 2s 10 # 每 2 秒打印一次,共 10 次
pid:目标 Java 进程 ID(可通过jps获取)interval:采样间隔(如1s、500ms)count:采样次数(可选)
输出字段详解(以 JDK 8+ 默认 G1 或 Parallel GC 为例)
执行 jstat -gc 12345 后,典型输出如下:
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
1024.0 1024.0 0.0 1024.0 8192.0 8192.0 20480.0 18432.0 65536.0 63488.0 8192.0 7936.0 25 1.250 3 2.100 3.350
各列含义(单位:KB)
| 列名 | 全称 | 含义 |
|---|---|---|
| S0C | Survivor 0 Capacity | Survivor 0 区当前容量(KB) |
| S1C | Survivor 1 Capacity | Survivor 1 区当前容量(KB) |
| S0U | Survivor 0 Utilization | Survivor 0 区已使用空间(KB) |
| S1U | Survivor 1 Utilization | Survivor 1 区已使用空间(KB) |
| EC | Eden Capacity | Eden 区当前容量(KB) |
| EU | Eden Utilization | Eden 区已使用空间(KB) |
| OC | Old Capacity | 老年代(Old Gen)当前容量(KB) |
| OU | Old Utilization | 老年代已使用空间(KB) |
| MC | Metaspace Capacity | 元空间(Metaspace)总容量(JDK 8+ 取代 PermGen)注意:这是 committed 内存,不是 reserved |
| MU | Metaspace Utilization | 元空间已使用空间(KB) |
| CCSC | Compressed Class Space Capacity | 压缩类指针空间容量(KB) |
| CCSU | Compressed Class Space Used | 压缩类指针空间已使用(KB) |
| YGC | Young GC Count | Young GC(Minor GC)发生次数 |
| YGCT | Young GC Time | Young GC 累计耗时(秒) |
| FGC | Full GC Count | Full GC 发生次数 |
| FGCT | Full GC Time | Full GC 累计耗时(秒) |
| GCT | Total GC Time | 所有 GC 累计总耗时(秒) = YGCT + FGC |
步骤 3:结合现象判断问题类型
jstat -gcutil
| 现象 | 可能原因 |
|---|---|
| Old 区持续增长,Full GC 后不下降 | 内存泄漏(如静态集合缓存未清理、监听器未注销) |
| Eden 区满后 Young GC 无法回收,直接 Full GC | 大对象直接进入 Old 区,或 Survivor 区过小 |
| Metaspace (M) 持续增长 | 动态类加载过多(如 Groovy 脚本、反射生成类未卸载) |
| 频繁 Full GC 但 Old 区使用率不高 | 可能是 Metaspace OOM 或堆外内存问题(需结合其他工具) |
jstat -gc
- 判断是否内存泄露
- OU(老年代使用量)持续增长,即使经过 Full GC 后也不下降 → 内存泄漏
- 对比多次采样:
OU从 10000 → 15000 → 18000(Full GC 后仍高)
- 判断GC压力
- YGC 频繁(如每秒多次)且 EU 快速打满 → 对象分配速率过高
- FGC 次数增加 或 FGCT 单次耗时长(如 >1s)→ 老年代压力大,可能触发 STW 停顿
- Survivor 区行为
- 正常情况下,S0 和 S1 只有一个在使用(另一个为 0),GC 后会交换角色
- 如果 S0U 和 S1U 同时非零,可能是 GC 日志未刷新或特殊 GC 算法(如 G1)
- 元空间(Metaspace)问题
- MU 接近 MC 且持续增长 → 可能动态生成类过多(如 Groovy、反射、CGLib)
- 若出现
java.lang.OutOfMemoryError: Metaspace,需检查类加载器泄漏
⚠️ 注意:MC 并非最大限制,而是当前已向 OS 申请的 committed 内存。元空间默认无上限(受 -XX:MaxMetaspaceSize 限制)。
步骤 4:进一步抓取堆转储(Heap Dump)
若确认是堆内存泄漏,立即生成 heap dump 供分析:
# 生成堆转储(注意:会暂停应用,谨慎在高峰期使用)
jmap -dump:format=b,file=/tmp/heap.hprof 12345
然后使用 MAT(Memory Analyzer Tool) 或 JProfiler 分析:
- 查看 Dominator Tree 找出占用内存最大的对象
- 检查 GC Roots 路径,确认为何对象未被回收