1. thread 命令示例:排查 CPU 占用过高
场景: 线上监控显示某个 Java 服务的 CPU 占用率突然飙升到 90% 以上,需要快速定位是哪个线程导致的。
命令及输出:
-
查找占用 CPU 最高的线程:
$ thread -n 3 -
结果解读:
PID Name CPU % State 52 http-nio-8080-exec-1 98 RUNNABLE 49 http-nio-8080-exec-2 1 WAITING 50 Timer-0 0 WAITING Arthas 自动将 CPU 占用率最高的线程信息放在了最前面。我们可以看到
http-nio-8080-exec-1这个线程占用了 98% 的 CPU,并且处于RUNNABLE状态。 -
查看该线程的堆栈信息(关键步骤):
从上面的输出中,我们会看到线程 52 的完整堆栈,例如:
"http-nio-8080-exec-1" Id=52 RUNNABLE
at com.example.service.UserService.calculateSomething(UserService.java:120)
- locked <0x1b2c4d8e> (a java.lang.String)
at com.example.controller.UserController.getUser(UserController.java:45)
...
- 结论: 堆栈信息显示,线程 52 正在
UserService.calculateSomething(UserService.java:120)这个方法中执行,并且持续消耗 CPU。检查该方法代码后,发现其中有一个没有退出条件的死循环或计算复杂度极高的逻辑,从而定位问题。
2. trace 命令示例:定位请求慢的瓶颈点
场景: 用户反馈一个查询接口响应时间过长(例如 5 秒),想要知道这 5 秒都耗在了请求链路中的哪个环节。
命令:
我们要追踪 com.example.controller.OrderController 类中的 queryOrder 方法。
$ trace com.example.controller.OrderController queryOrder
结果解读:
输出会显示方法调用树和每一步的耗时:
`---ts=2025-11-19 22:45:00; [2000.0ms] com.example.controller.OrderController:queryOrder()`
`---[10.0ms] com.example.service.OrderService:validate(int)` // 参数校验很快
`---[4900.0ms] com.example.service.OrderService:queryFromDatabase(int)` // **瓶颈点!**
`---[80.0ms] com.example.util.DataConverter:format(java.lang.Object)` // 数据格式化
结论:
通过 trace 的精确计时,我们发现 queryFromDatabase 这个方法执行耗时 4900 毫秒,几乎占用了整个请求时间。这表明数据库查询是主要的性能瓶颈。下一步就可以针对这个方法进行优化(例如检查 SQL 语句、索引等)。
3. watch 命令示例:观测方法执行参数和返回值
场景: 线上一个注册接口偶尔会抛出空指针异常(NPE),但无法复现,想看看是什么样的输入参数导致了 NPE。
命令:
我们想要观测 com.example.service.UserService 类中的 register 方法,打印其入参和异常信息。
$ watch com.example.service.UserService register "{params, throwExp}" -e -x 2
参数解释:
"{params, throwExp}":OGNL 表达式,表示我们想要观测的字段(入参和抛出的异常)。-e:表示只在方法抛出异常时才进行观测。-x 2:表示输出结果的属性展开深度为 2,避免输出过多信息。
结果解读(假设异常发生):
当发生空指针异常时,Arthas 会打印类似以下信息:
ts=2025-11-19 22:48:30; [ERROR]
... throwExp: java.lang.NullPointerException
at com.example.service.UserService.register(UserService.java:70)
...
params: 0: com.example.model.UserRegisterRequest@2b93f187 email=user@example.com
username=arthas_user password=***
// **关键发现:**
// address=null address=<null>
结论:
从观测结果中发现,当 UserRegisterRequest 对象的 address 字段为 null 时,方法抛出了 NullPointerException。结合代码,发现 register 方法在处理 address 前没有进行空值检查。这使得问题得到了精确的定位,无需修改代码,就能知道是哪个输入值导致了问题。