Java

线上排障时,jstack、jmap、jstat 我通常怎么决定先拿哪一个

`jstack`、`jmap`、`jstat` 不难记,难的是线上现场先动哪一个、为什么不先动另外两个。把它们放回 CPU 高、GC 抖动、疑似内存问题这几类现场里,工具边界会比平均铺开讲分工更清楚。

  • Java
  • jstack
  • jmap
  • jstat
14 分钟阅读

jstackjmapjstat 这三个名字,做 Java 的人基本都见过。

平时背起来也不难:

  • jstack 看线程
  • jmap 看内存
  • jstat 看 GC

但真正到线上,麻烦往往不是记不住它们干什么,而是你会在一个很真实的几秒钟里犹豫:

  • 现在 CPU 高、RT 抖、线程也在堆,是先抓线程,还是先看 GC?
  • 看到 Full GC 变多,是不是该直接 dump 堆?
  • 明明怀疑内存,为什么我还是会先去看线程栈?

我后来越来越觉得,这三个工具不该平均铺开讲分工。因为线上现场不是考试,不是让你把三把工具都介绍一遍,而是逼你先做决定:

此刻我先拿哪一个,为什么不是另外两个?

所以这篇只讲我更常遇到的几类现场:

  1. CPU 高、请求卡住、线程堆着不动时,为什么我通常先拿 jstack
  2. RT 抖、Full GC 变多、怀疑 JVM 已经在喘时,为什么我会先拿 jstat
  3. 已经很像内存问题甚至奔着 OOM 去时,为什么我才会考虑 jmap

这三把工具都会讲到,但重点不在百科式分工,而在出手顺序。

一、CPU 高、线程卡住、请求不返回时,我通常先拿 jstack

这类现场最常见,也最容易让人乱。

告警常常是一起来的:

  • Java 进程 CPU 高
  • 某些接口 RT 拉长
  • 线程池 active 很高
  • 群里有人开始问是不是 GC 在抖

这时候我第一反应通常不是 jstat,更不是 jmap,而是先抓线程栈。

为什么先看 jstack

因为这时候我最想先回答的问题,不是“GC 有没有问题”,也不是“堆里谁最多”,而是:

线程现在到底在干什么?

我需要先分清几件事:

  • 是几个热点线程把 CPU 打上去了
  • 还是一批线程都在等锁、等数据库、等下游
  • 还是线程池堆积只是结果,真正忙的是别的东西

jstack 对这种现场最有用的地方,就是它直接把线程状态摊给你看。

jstack <pid>

如果时间允许,我一般不会只抓一次,而是隔几秒连续抓两三份。因为单次线程栈只是瞬时快照,连续几份重复停在相似位置,怀疑才更扎实。

jstack 12345 > /tmp/jstack-1.log
sleep 3
jstack 12345 > /tmp/jstack-2.log
sleep 3
jstack 12345 > /tmp/jstack-3.log

为什么这时我通常不先拿 jstat

不是说 jstat 没用,而是它回答的是另一类问题:GC 趋势、内存区域变化、Full GC 是否频繁。

可如果我眼前最急的是“请求为什么挂着、线程为什么不动、CPU 到底烧在哪”,那线程现场优先级更高。

就算后面要补 jstat,通常也是在线程栈已经告诉我“像不像 GC 牵出来的问题”之后。

为什么更不会先上 jmap

因为这时现场还远没收窄到“堆里到底是谁占了内存”那一步。

jmap 尤其是 dump 堆,是更重的动作。在线上高压、方向未明的时候,我不会先为了“万一有帮助”去做一件更重的事。

换句话说,这类现场里,jstack 不是因为它最全,而是因为它最贴近我此刻的问题。

二、RT 抖、Full GC 变多、怀疑 JVM 在喘时,我会先拿 jstat

还有一类现场,第一眼看上去就不像“某几个线程的局部问题”,而更像 JVM 整体状态在变差。

比如:

  • RT 呈波浪式抖动
  • Full GC 次数明显变多
  • CPU 不一定特别高,但服务总是一阵一阵卡
  • Old 区像是下不来

这类场景里,我通常先拿的是 jstat

jstat -gcutil <pid> 1000 10

为什么先看 jstat

因为我这时最想先确认的是:

GC 到底是不是主角?还是只是陪跑?

jstat 的价值,在于它能很快告诉我:

  • Eden、Old 区是不是在持续抬高
  • Full GC 是偶发,还是已经频繁到足以解释 RT 抖动
  • 回收之后内存能不能下来

它不像 jstack 那样告诉你线程此刻停在哪,但它很适合判断 JVM 是否已经进入一种“整体呼吸不顺”的状态。

为什么这时我不急着先抓 jstack

不是不能抓,而是优先级不一样。

如果现场一看就像 GC 压力主导,比如 Full GC 数量明显变多、内存水位迟迟下不来,那先确认趋势更重要。因为这会直接决定我后面是不是继续往内存方向深挖。

当然,如果 jstat 看下来并不夸张,我还是会很自然回去补 jstack。但那时线程栈是作为第二步,而不是起手式。

为什么我也不会直接跳到 jmap

因为 jstat 在这里像一个筛子。

它先回答:

  • 这是不是值得往内存方向继续查
  • 现在是短时抖动,还是持续恶化
  • 这个方向值不值得付出更重的分析成本

如果 jstat 看起来并没有明显异常,我通常不会贸然去 dump 堆。

三、已经像内存问题,甚至在奔着 OOM 去时,我才考虑 jmap

真正轮到 jmap,通常说明我心里已经有了更强的判断:

  • Old 区长期高位不下
  • Full GC 越来越频繁,但回收效果差
  • 服务已经接近 OOM,或者刚发生过 OOM
  • 我怀疑有对象膨胀、缓存打穿、生命周期异常之类的问题

这时候我需要回答的问题,已经不是线程在忙什么,也不是 GC 是否频繁,而是:

堆里到底是谁把内存占住了?

这时 jmap 才真正上场。

常见动作一般是这几类:

jmap -histo <pid>
jmap -heap <pid>
jmap -dump:live,format=b,file=/tmp/heap.hprof <pid>

为什么我把 jmap 放在更后面

因为它更适合“已经确认方向后做深挖”,不适合“方向还没稳时先打一枪再说”。

尤其是 dump 堆这件事,本身就比 jstackjstat 更重,还要考虑:

  • 现场压力能不能承受
  • 磁盘空间够不够
  • 有没有从节点、副本或替代实例可分析

如果只是因为“这三个工具总得轮到一个”,就直接上 jmap,往往不是最稳的决策。

为什么这时不再把 jstack 当主角

到了这一步,线程信息当然仍然可能有帮助,但它更像辅助。

因为你真正要抓的,是对象分布、占用大头、是否存在异常膨胀,而不是线程此刻卡在哪一行。

也就是说,不是 jstack 没用,而是问题已经换层了。

四、我在线上更常见的几个“先手决策”

如果把上面三类现场压成更短的判断,我现在基本是这样用的。

1. 请求挂住、CPU 高、线程池堆积

我通常先:

  • top -Hp <pid> 看高 CPU 线程
  • jstack 看热点线程或阻塞线程栈

因为这类现场最怕的是:你还没搞清线程在干什么,就先被 GC 或内存假象带跑。

2. RT 一阵一阵抖、Full GC 在涨

我通常先:

  • jstat -gcutil <pid> 1000 10

因为这时我先要判断,GC 到底是不是主导因素。

3. Old 区高位、回收效果差、已经非常像内存问题

我才会考虑:

  • jmap -histo
  • 必要时 jmap -dump

因为这时才轮到“堆里到底是谁”的问题。

4. CPU 高、GC 也高、线程也堆着,三个信号一起上来怎么办

这是最容易犹豫的场景。

我的习惯通常是:

  • 如果接口已经挂住、线程堆积明显,我先 jstack
  • 如果服务更像周期性喘不过气,Full GC 也很扎眼,我先 jstat
  • 只有在 jstat 已经很明确地把方向指向内存后,我才往 jmap

它不是固定答案,但核心还是同一句:先拿最接近当前问题的那把工具。

五、这三把工具真正难的,不是命令本身,而是别在没到那一步时提前出手

我自己最常见的几个提醒是:

1. 不要把三把工具当固定套餐

线上不是“都打一遍更保险”,而是“先打哪个更能缩小范围”。

2. jstack 是看线程现场,不是长期趋势

所以连续抓几份,通常比单次快照更有判断力。

3. jstat 很适合判断趋势,但不直接告诉你对象是谁

它更像温度计,不像 CT。

4. jmap 最接近真相,但也最重

所以它最适合在方向已经比较稳时出手,而不是一上来碰碰运气。

六、如果你只想记一个最实用的版本,我建议记这个

  • 线程像主角时,先 jstack
  • GC 像主角时,先 jstat
  • 对象占用像主角时,再 jmap

把这句话记住,比把三把工具平均介绍一遍更接近线上。

因为真正的现场决策,从来不是“它们分别能干什么”,而是:

我现在最想先排掉哪一种误判,所以先拿谁。