线上CPU飙高,不重启怎么定位是哪行代码的问题

生产环境突然CPU 100%,不能随便重启,也没法加日志。以前只能干瞪眼,现在用Arthas,不停机就能定位问题。

Arthas是什么

阿里开源的Java诊断工具,可以在不重启应用的情况下:

  • 查看哪个方法在消耗CPU
  • 动态查看方法入参和返回值
  • 查看方法调用链路和耗时
  • 热更新代码(线上慎用)
  • 查看类加载信息

官方地址:https://arthas.aliyun.com/

安装和启动

# 下载
curl -O https://arthas.aliyun.com/arthas-boot.jar

# 启动(会列出Java进程让你选)
java -jar arthas-boot.jar

# 或者直接指定PID
java -jar arthas-boot.jar <pid>

进入后会看到arthas的命令行界面。

实战场景一:CPU飙高定位

问题现象

服务器CPU持续100%,top看是Java进程。

排查步骤

Step 1:进入Arthas

java -jar arthas-boot.jar

Step 2:用thread命令找到CPU最高的线程

thread -n 3

输出:

"http-nio-8080-exec-15" Id=45 cpuUsage=98.5% RUNNABLE
    at com.example.service.OrderService.calculatePrice(OrderService.java:156)
    at com.example.controller.OrderController.createOrder(OrderController.java:78)
    ...

直接定位到OrderService.java的156行!

Step 3:查看具体代码

jad com.example.service.OrderService calculatePrice

反编译出源码,发现是个死循环或者复杂计算。

常用thread命令

# 查看最忙的3个线程
thread -n 3

# 查看所有线程状态
thread

# 查看死锁
thread -b

# 查看指定线程
thread 45

实战场景二:方法执行太慢

问题现象

某个接口响应时间突然变长,不知道慢在哪。

用trace命令追踪

trace com.example.service.OrderService createOrder

输出:

`---ts=2024-01-15 14:30:00;thread_name=http-nio-8080-exec-1;
    `---[3256.123ms] com.example.service.OrderService:createOrder()
        +---[1.234ms] com.example.service.OrderService:validateOrder()
        +---[3200.456ms] com.example.service.OrderService:queryInventory()  # 这里慢!
        +---[50.123ms] com.example.service.OrderService:saveOrder()

一眼就看出queryInventory方法耗时3200ms,问题定位到了。

trace进阶用法

# 只显示耗时超过100ms的
trace com.example.service.OrderService createOrder '#cost > 100'

# 追踪多层调用(默认1层)
trace com.example.service.OrderService createOrder -n 5 --skipJDKMethod false

# 追踪多个方法
trace com.example.service.* *

实战场景三:查看方法入参和返回值

问题现象

方法逻辑有问题,想看看实际传了什么参数。

用watch命令

# 查看入参
watch com.example.service.UserService getUser params

# 查看返回值
watch com.example.service.UserService getUser returnObj

# 同时看入参和返回值
watch com.example.service.UserService getUser '{params, returnObj}'

输出:

ts=2024-01-15 14:35:00; result=@ArrayList[
    @Object[][
        @Long[12345],
    ],
    @User[
        id=@Long[12345],
        name=@String["张三"],
        status=@Integer[1],
    ],
]

watch进阶用法

# 只在抛异常时显示
watch com.example.service.UserService getUser '{params, throwExp}' -e

# 显示调用前和调用后
watch com.example.service.UserService getUser '{params, returnObj}' -b -s

# 条件过滤:只看userId=123的调用
watch com.example.service.UserService getUser '{params, returnObj}' 'params[0]==123'

# 展开对象属性(默认只显示1层)
watch com.example.service.UserService getUser returnObj -x 3

实战场景四:动态修改日志级别

线上日志级别是INFO,想临时看DEBUG日志。

# 查看当前日志级别
logger

# 修改日志级别
logger --name com.example.service --level DEBUG

# 恢复
logger --name com.example.service --level INFO

不用重启,不用改配置文件。

实战场景五:查看JVM信息

# 查看JVM参数
jvm

# 查看内存使用
memory

# 查看系统属性
sysprop

# 查看环境变量
sysenv

# 实时面板(类似top)
dashboard

dashboard会显示实时的线程、内存、GC情况,很直观。

实战场景六:反编译线上代码

怀疑线上代码和本地不一致:

# 反编译整个类
jad com.example.service.OrderService

# 只看某个方法
jad com.example.service.OrderService createOrder

# 不显示行号
jad --source-only com.example.service.OrderService

实战场景七:查看类加载信息

# 查看类是从哪个jar加载的
sc -d com.example.service.OrderService

# 查看类的所有方法
sm com.example.service.OrderService

# 模糊搜索类
sc *OrderService*

排查类冲突、jar包冲突很有用。

常用命令速查表

命令 作用 常用示例
thread 查看线程 thread -n 3
trace 方法调用链路耗时 trace ClassName methodName
watch 查看方法入参/返回值 watch ClassName methodName '{params,returnObj}'
stack 查看方法调用栈 stack ClassName methodName
tt 时间隧道,记录调用 tt -t ClassName methodName
jad 反编译 jad ClassName
sc 查看类信息 sc -d ClassName
sm 查看方法信息 sm ClassName
logger 查看/修改日志级别 logger --name xxx --level DEBUG
dashboard 实时面板 dashboard
memory 内存信息 memory
heapdump 导出堆快照 heapdump /tmp/dump.hprof

tt命令:时间隧道

记录方法调用,可以事后回放:

# 开始记录
tt -t com.example.service.OrderService createOrder

# 查看记录列表
tt -l

# 查看某次调用的详情
tt -i 1000

# 重新执行某次调用(危险!)
tt -i 1000 -p

ognl表达式

Arthas支持ognl表达式,可以做很多事:

# 调用静态方法
ognl '@java.lang.System@getProperty("java.version")'

# 获取Spring Bean并调用方法
ognl '@com.example.SpringContextHolder@getBean("userService").getUser(123)'

# 查看静态变量
ognl '@com.example.config.AppConfig@MAX_RETRY_COUNT'

远程诊断

如果服务器在内网没有公网IP,可以用星空组网把本地和服务器连起来。组网后直接SSH上去运行Arthas,或者用Arthas的tunnel server做Web端诊断,比开VPN方便。

退出Arthas

# 退出当前会话,不影响目标进程
quit

# 完全退出,从目标进程卸载Arthas
stop

注意事项

  1. 生产环境慎用:trace、watch等命令有性能开销
  2. 用完记得stop:否则Arthas会一直attach在进程上
  3. 权限控制:Arthas能做的事很多,注意权限管理
  4. 版本匹配:Arthas版本和JDK版本要兼容

总结

场景 命令
CPU飙高 thread -n 3
方法慢 trace
看入参返回值 watch
看调用栈 stack
改日志级别 logger
反编译代码 jad
类加载问题 sc -d

Arthas是Java程序员的瑞士军刀,建议每个Java开发都要熟练掌握。

posted @ 2025-12-11 16:14  花宝宝  阅读(23)  评论(0)    收藏  举报