线上排障时,jstack、jmap、jstat 我通常怎么决定先拿哪一个
`jstack`、`jmap`、`jstat` 不难记,难的是线上现场先动哪一个、为什么不先动另外两个。把它们放回 CPU 高、GC 抖动、疑似内存问题这几类现场里,工具边界会比平均铺开讲分工更清楚。
jstack、jmap、jstat 这三个名字,做 Java 的人基本都见过。
平时背起来也不难:
jstack看线程jmap看内存jstat看 GC
但真正到线上,麻烦往往不是记不住它们干什么,而是你会在一个很真实的几秒钟里犹豫:
- 现在 CPU 高、RT 抖、线程也在堆,是先抓线程,还是先看 GC?
- 看到 Full GC 变多,是不是该直接 dump 堆?
- 明明怀疑内存,为什么我还是会先去看线程栈?
我后来越来越觉得,这三个工具不该平均铺开讲分工。因为线上现场不是考试,不是让你把三把工具都介绍一遍,而是逼你先做决定:
此刻我先拿哪一个,为什么不是另外两个?
所以这篇只讲我更常遇到的几类现场:
- CPU 高、请求卡住、线程堆着不动时,为什么我通常先拿
jstack - RT 抖、Full GC 变多、怀疑 JVM 已经在喘时,为什么我会先拿
jstat - 已经很像内存问题甚至奔着 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 堆这件事,本身就比 jstack、jstat 更重,还要考虑:
- 现场压力能不能承受
- 磁盘空间够不够
- 有没有从节点、副本或替代实例可分析
如果只是因为“这三个工具总得轮到一个”,就直接上 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
把这句话记住,比把三把工具平均介绍一遍更接近线上。
因为真正的现场决策,从来不是“它们分别能干什么”,而是:
我现在最想先排掉哪一种误判,所以先拿谁。