欢迎光临
我们一直在努力

jstat /jstack/jmap 详解 + 实战场景示例

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=12345
  • top -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.log

    jstack 会自动检测死锁,日志末尾会输出 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 获取 pid
  • jstat -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、内存占用; 各司其职。

    赞(0)
    未经允许不得转载:171主机测评 » jstat /jstack/jmap 详解 + 实战场景示例
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址