Skip to content
Go back

Java线上排查——从top到Arthas的完整工具链

Java 线上排查工具链:从 top 到 Arthas

一句话结论(30s)

线上排查的关键是按”现象 → 指标 → 代码”逐层下钻——因为 CPU 飙高、内存 OOM、接口慢、死锁各有对应的工具链(top/jstack、dump/MAT、Arthas trace/watch、jstack 死锁检测),用对工具才能从”服务器指标异常”一路定位到”具体线程或方法”。

核心原理(2min)

底层深入(5-10min)

场景一:CPU 飙高

1. top -c                   → 找到高 CPU 的 Java 进程 PID
2. top -H -p <pid>          → 找到该进程中最高 CPU 的线程 TID
3. printf '%x\n' <tid>      → 将 TID 转十六进制 0xtid
4. jstack <pid> | grep 0xtid -A 20  → 看这个线程正在执行什么代码

通常能定位到:死循环中的某个方法、反复 Full GC 的 GC 线程、或等待锁的热点代码。

思考穿插:为什么排查 CPU 飙高要先”进程 → 线程 → 十六进制 TID → jstack”这么绕一圈,而不是直接看日志?因为日志是事后记录、且高 CPU 的死循环往往不打日志——你只能靠”实时抓那个正在烧 CPU 的线程”来定位。这一步的关键是理解 Linux 线程 TID 和 jstack 里十六进制 nid 的对应关系,否则拿到了线程号也匹配不上代码。

场景二:内存 OOM

1. -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/dump.hprof
   → OOM 时自动 dump
2. MAT 打开 dump 文件 → Leak Suspects → 自动找最大保留对象
3. Dominator Tree → 看你怀疑的大对象的引用链

MAT 的”支配树”直观展示每个对象”统治”了多少内存。一个 HashMap 持有百万个 Entry → 支配树中 HashMap 的”保留大小”就是几 GB。

场景三:接口慢/无响应

Arthas trace 命令追踪方法调用链的每步耗时

trace com.example.ShopService getShopDetail -n 5
# 输出:
# `---[150.3ms] ShopService:getShopDetail()
#     +---[80.2ms] getShop()        ← MySQL 查询最慢
#     +---[5.1ms]  getScore()       ← Redis 正常
#     +---[30.0ms] getHotBlogs()
#     `---[35.0ms] searchNearby()

精确知道哪一步耗时最多,无需加日志重启。

思考穿插:为什么 Arthas trace 能”不重启、不加日志”就拿到方法级耗时?因为它基于字节码增强,在运行期动态往目标方法里插入计时逻辑——这正好弥补了传统排查的两大痛点:加日志要重新发布、重新发布还可能破坏现场。理解”字节码增强”这个原理,你就明白它为什么能无侵入地做到这一切。

Arthas watch 看入参、返回值、异常:

watch com.example.OrderService createOrder '{params, returnObj, throwExp}' -x 3

场景四:死锁

jstack <pid> | grep -A 50 "Found one Java-level deadlock"

jstack 自动检测死锁——线程 A 持有锁 X 等锁 Y,线程 B 持有锁 Y 等锁 X → jstack 直接告诉你”找到了 Java 级死锁”。

Arthas redefine 热更新

redefine /path/to/UpdatedClass.class

底层调 JVMTI redefineClasses()——JVM 用新字节码替换方法体,JIT 缓存失效重新编译。限制:不能新增/删除字段或方法(只能改方法体),不能改继承层次。运行中的方法实例不受影响(当前栈帧继续执行旧方法直到返回)。

适合临时 debug / 紧急逻辑修复。真正的 hotfix 仍需走正式发布,因为 redefine 重启后失效。

思考穿插:为什么 redefine 只能改方法体、不能增删字段或方法?因为 JVMTI 的 redefineClasses 本质是”用新字节码替换已有方法体”,但类的结构(字段、方法签名、继承关系)已被加载进 JVM、关系到对象内存布局,运行时无法安全改动——这是 JVM 类加载的硬约束,不是 Arthas 偷懒。

总结

问题命令关键工具
CPU 高top → jstack线程栈分析
内存高jmap -dump → MAT支配树 + Leak Suspects
接口慢Arthas trace方法级耗时分布
死锁jstack自动检测 Found deadlock
热修复Arthas redefineJVMTI redefineClasses

章末提问

线上排查是最值得深挖的场景题,对方通常会顺着”为什么用这个工具”往下追。以下是三个典型追问:

  1. “CPU 飙高,你的完整排查步骤是什么?” 结论先行:「top 定位进程 → top -H -p 定位线程 → TID 转十六进制 → jstack 匹配线程栈,找到正在执行的代码。」因为 对方要看你会不会”逐层下钻”——从服务器指标到具体线程再到代码行,这套链路能证明你有真实排查经验,而不是只会背命令。

  2. jstack 和 Arthas trace 分别适合什么场景?” 结论先行:「jstack 看线程状态、死锁、阻塞等”瞬间快照”;trace 看方法调用链每一步的耗时分布,适合接口慢的定位。」因为 对方在考你的”工具选择判断力”——能用对工具说明你理解每个工具的定位边界,而不是把 jstack 当万能钥匙。

  3. “Arthas redefine 热更新为什么不能替代正式发布?” 结论先行:「因为底层 JVMTI 只允许改方法体、不能增删字段/方法,且重启后失效——它只适合临时止血,正式修复仍要走发布流程。」因为 这题在验证你理解”热更新的能力边界”——能说出 JVMTI 的限制和”重启失效”,证明你不是把 redefine 当成随意改代码的工具。


Share this post on:

Previous Post
AQS——JUC所有锁的共同底座
Next Post
synchronized锁升级机制