Arthas 实战指南:从方法耗时定位到 JVM 变量热修改

2026-07-13 19:46

Java Arthas 线上排障 2026-07-13

线上问题排查是每个后端开发者的日常难题。当接口突然变慢、配置莫名失效、变量值不符合预期时,我们往往需要在不重启服务的前提下,快速定位问题根因。Arthas(Alibaba Java Diagnostic Tool)正是为此而生——它让我们在线上环境"透视" JVM 内部状态,堪称 Java 排障的"瑞士军刀"。

在千阶智能的技术团队中,Arthas 已经是线上排障的标准工具。本文结合千阶智能平台(qjchat.com)真实生产案例,系统讲解 Arthas 五大核心用法,助你从"会敲命令"进阶到"能排障"。


一、观察方法耗时——trace / stack

1.1 命令说明

命令用途前提
trace追踪方法调用链及每一层耗时有流量经过实例
stack输出方法被调用的调用路径有流量经过实例

1.2 实战案例:接口慢在哪里?

千阶智能外呼系统接口出现响应超时,第一步就是用 trace 逐层定位耗时瓶颈:

# 追踪 WordQueryAPIImpl.getWord 的调用链耗时
trace com.qianjie.smart.api.word.WordQueryAPIImpl getWord

输出结果会以树状结构展示每一层调用的耗时,一目了然地看到哪个子方法是"罪魁祸首":

`---[123.45ms] com.qianjie.smart.api.word.WordQueryAPIImpl:getWord()
    +---[2.31ms] com.qianjie.smart.api.word.WordQueryAPIImpl:validateParam() #45
    +---[5.67ms] com.qianjie.smart.api.word.WordQueryAPIImpl:preProcess() #52
    +---[110.23ms] com.qianjie.smart.api.word.WordQueryAPIImpl:doResponse() #68
    `---[1.12ms] com.qianjie.smart.api.word.WordQueryAPIImpl:postProcess() #75

从输出可以看到 doResponse 耗时 110ms,是整个接口的瓶颈。假设发现 doResponse 中主要是 transform2RespWithFilter 在耗时,则继续深入:

# 继续追踪下一层
trace com.qianjie.smart.factory.ModelFactoryImpl transform2RespWithFilter
`---[105.89ms] com.qianjie.smart.factory.ModelFactoryImpl:transform2RespWithFilter()
    +---[0.45ms] com.qianjie.smart.factory.ModelFactoryImpl:getFilterList() #120
    +---[98.76ms] com.qianjie.smart.filter.WordFilterChain:doFilter() #135
    |   +---[50.12ms] com.qianjie.smart.filter.WordMatchFilter:doFilter() #45
    |   `---[45.67ms] com.qianjie.smart.filter.WordRankFilter:doFilter() #78
    `---[3.21ms] com.qianjie.smart.factory.ModelFactoryImpl:buildResponse() #150

可以看到 WordFilterChain:doFilter 耗时 98ms,其中 WordMatchFilterWordRankFilter 分别消耗了 50ms 和 45ms,这就是根因所在。

排障思路:像剥洋葱一样,从入口方法开始,逐层 trace,直到定位到真正的耗时节点。

1.3 进阶用法

# 过滤耗时大于 100ms 的调用
trace com.example.Service method '#cost > 100'

# 排除 jdk 内部调用路径
trace com.example.Service method --exclude-class-pattern java.*

# 追踪多次调用(默认只追踪一次)
trace com.example.Service method -n 5

二、观察方法入参、返回值——watch

2.1 命令说明

watch 是 Arthas 中最灵活的命令之一,可以观察方法的入参(params)返回值(returnObj)异常(throwExp)以及目标对象(target)

观察点参数说明
方法调用前-b观察入参
方法返回后默认观察返回值
方法异常后-e观察异常
同时观察-b -s -e全生命周期

2.2 实战案例:连接池配置是否生效?

问题背景:千阶智能平台配置了连接池最大值为 200,但疑似未生效。

配置文件如下:

spring:
  datasource:
    hikari:
      maximum-pool-size: 200

通过 Arthas 验证实际生效值:

# 只查看最大连接数
watch com.zaxxer.hikari.HikariConfig getMaximumPoolSize
Affect(class count: 1, method count: 1) cost in 45 ms, listenerId: 1
method=com.zaxxer.hikari.HikariConfig.getMaximumPoolSize location=AtExit
ts=2026-07-13 10:23:45; result=@Integer[10]
# 查看所有属性(展开2层)
watch com.zaxxer.hikari.HikariConfig getMaximumPoolSize -x 2
Affect(class count: 1, method count: 1) cost in 38 ms, listenerId: 2
method=com.zaxxer.hikari.HikariConfig.getMaximumPoolSize location=AtExit
ts=2026-07-13 10:24:12; result=@Integer[10]
  @HikariConfig[
    maximumPoolSize=@Integer[10],
    minimumIdle=@Integer[10],
    idleTimeout=@Long[600000],
    maxLifetime=@Long[1800000],
    connectionTimeout=@Long[30000],
    ...
  ]

结果分析

  • 配置最大连接数为 200,实际生效为 10 → 不符合预期,配置未生效
  • 配置最大连接数为 5,实际生效为 5 → 符合预期

关键洞察watch 让我们在不重启、不加日志的情况下,直接"看到"运行时的变量值,快速验证配置是否按预期加载。

2.3 进阶用法

# 观察入参和返回值
watch com.example.Service method '{params, returnObj}' -x 3

# 按条件过滤(只观察第一个参数大于 100 的调用)
watch com.example.Service method '{params, returnObj}' 'params[0] > 100'

# 观察异常
watch com.example.Service method '{params, throwExp}' -e

# 观察调用耗时
watch com.example.Service method '#cost'

三、获取与修改 JVM 变量——ognl

这是 Arthas 最强大的能力之一:在运行时读取甚至修改 JVM 内部的任意变量,无需重启服务。

3.1 静态变量

# 获取静态变量
ognl '@com.example.Config@DEFAULT_VALUE'

# 修改静态变量
ognl '@com.example.Config@DEFAULT_VALUE="new_value"'

3.2 实例变量

场景获取方式修改方式
有流量通过watchwatch + ognl 表达式
无流量通过springbean + ognlspringbean + ognl

3.3 实战案例:无流量时修改实例变量

千阶智能平台在凌晨无流量时段,需要紧急修改某个业务服务的实例变量。

第一步:准备 BeanFactoryHelper

在项目中新增一个静态 Bean 工厂类,通过 Spring 的 BeanFactoryAware 回调获取 BeanFactory

@Component
public class BeanFactoryHelper implements BeanFactoryAware {
    private static BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        BeanFactoryHelper.beanFactory = beanFactory;
    }

    public static Object getBean(String beanName) {
        return beanFactory.getBean(beanName);
    }

    public static <T> T getBean(Class<T> requiredType) {
        return beanFactory.getBean(requiredType);
    }

    public static boolean containsBean(String beanName) {
        return beanFactory.containsBean(beanName);
    }
}

第二步:获取目标类的 ClassLoader Hash

sc -df com.qianjie.smart.infrastructure.bs.BsQueryService

输出中重点关注 classLoaderHash,后续 ognl 命令需要用到:

 class-info        com.qianjie.smart.infrastructure.bs.BsQueryService
 code-source       file:/home/work/java/smart-app/target/smart-app.jar!/BOOT-INF/lib/smart-infrastructure-1.0.0.jar!/
 name              com.qianjie.smart.infrastructure.bs.BsQueryService
 isInterface       false
 isAnnotation      false
 isEnum            false
 isAnonymousClass  false
 isArray           false
 isLocalClass      false
 isMemberClass     false
 isPrimitive       false
 isSynthetic       false
 simple-name       BsQueryService
 modifier          public
 annotation        org.springframework.stereotype.Component
 interfaces
 super-class       +-java.lang.Object
 class-loader      +-org.springframework.boot.loader.LaunchedURLClassLoader@6767c1fc
                     +-jdk.internal.loader.ClassLoaders$AppClassLoader@2f333739
                       +-jdk.internal.loader.ClassLoaders$PlatformClassLoader@1172f803
 classLoaderHash   6767c1fc
 fields            name     log
                   type     org.slf4j.Logger
                   modifier final,private,static
                   value    Logger[com.qianjie.smart.infrastructure.bs.BsQueryService]

                   name     protocolSeparator
                   type     java.lang.String
                   modifier

                   name     enter
                   type     java.lang.String
                   modifier

                   name     defaultKey
                   type     java.lang.String
                   modifier

第三步:查看变量

ognl -c 6767c1fc '#instance=@com.qianjie.smart.utils.BeanFactoryHelper@getBean("bsQueryService")'

输出:

@BsQueryService[
    log=@Logger[Logger[com.qianjie.smart.infrastructure.bs.BsQueryService]],
    protocolSeparator=@String[\r\n.\r\n],
    enter=@String[\r\n],
    defaultKey=@String[99],
]

第四步:修改变量

ognl -c 6767c1fc '#instance=@com.qianjie.smart.utils.BeanFactoryHelper@getBean("bsQueryService"),
#fieldObj=@com.qianjie.smart.infrastructure.bs.BsQueryService@class.getDeclaredField("defaultKey"),
#fieldObj.setAccessible(true),
#fieldObj.set(#instance,"88")'

修改后再次观察,确认 defaultKey 已从 "99" 变为 "88"

@BsQueryService[
    ...
    defaultKey=@String[88],
]

核心原理:通过 Spring BeanFactory 获取实例引用,再通过反射 setAccessible(true) 突破访问控制,直接修改私有字段值。整个过程无需重启 JVM。


四、类加载与热替换 Class

当线上代码有 Bug 但无法立即发版时,Arthas 支持热替换 Class 文件:

# 查看类加载信息
sc -df com.example.TargetClass

# 反编译线上代码(确认当前逻辑)
jad com.example.TargetClass methodName

# 热替换(用本地编译好的 class 文件替换线上的)
redefine /path/to/TargetClass.class

注意事项

  • redefine 后原来的类不能被 GC,需注意内存影响
  • 优先使用 retransform,它支持回滚操作
  • 热替换是临时方案,务必在下一版本中正式修复

五、CPU 与内存热点分析

5.1 CPU 线程热点

# 查看最消耗 CPU 的线程
thread -n 5

# 查看指定线程的堆栈
thread <thread-id>

# 查找死锁
thread -b

5.2 CPU 方法热点

# 持续采样,输出最耗 CPU 的方法
profiler start --event cpu
# ... 运行一段时间 ...
profiler stop

5.3 内存方法热点

# 分析内存分配热点
profiler start --event alloc
# ... 运行一段时间 ...
profiler stop

建议:profiler 采样时间建议 30s-60s,太短数据不准,太长影响性能。


六、经典排障案例总结

Case 1:接口太慢,定位耗时根因

trace 入口方法 → 发现慢子方法 → trace 子方法 → 逐层深入 → 定位根因

关键:像剥洋葱一样逐层追踪,不要一次 trace 太深。

Case 2:变量值是否符合预期

场景命令
有流量经过watch 观察入参/返回值
无流量经过springbean + ognl 直接读取

Case 3:配置中心丢失,需紧急修改配置

场景:千阶智能配置中心挂了,不能重启(一重启就挂),但需要修改某个 VIP 地址或域名。

方案一:Arthas 热修改

# 通过 springbean + ognl 直接修改 JVM 内部的配置变量
ognl -c <classLoaderHash> '#instance=@com.example.BeanFactoryHelper@getBean("configBean"),
#field=@com.example.ConfigBean@class.getDeclaredField("targetVip"),
#field.setAccessible(true),
#field.set(#instance,"10.10.10.10")'

方案二:内核四层转发(需 root 权限)

# 配置 iptables 将旧 VIP 的流量转发到新 VIP
iptables -t nat -A PREROUTING -d 10.11.12.13 -j DNAT --to-destination 10.10.10.10

方案选择:优先用 Arthas 热修改(影响范围小),只有在 Arthas 无法触达的场景才用 iptables。


七、Arthas 命令速查表

场景命令说明
方法耗时trace 类名 方法名逐层追踪调用链耗时
调用路径stack 类名 方法名查看方法被谁调用
入参/返回值watch 类名 方法名 '{params,returnObj}'观察方法入参和返回值
静态变量ognl '@类名@变量名'读取/修改静态变量
实例变量ognl + springbean读取/修改实例变量
类信息sc -df 类名查看类加载信息
反编译jad 类名 方法名查看线上代码
热替换redefine /path/to/Class.class热替换 Class 文件
CPU 热点线程thread -n 5Top 5 CPU 消耗线程
死锁检测thread -b查找死锁
性能采样profiler start/stopCPU/内存热点分析

结语

Arthas 的核心价值在于不重启、不侵入、实时诊断。掌握 tracewatchognl 三板斧,足以应对 80% 的线上排障场景。而 redefineprofiler 则是进阶利器,在紧急修复和性能调优中发挥关键作用。

更多千阶智能技术实践文章,请访问 qjchat.com

排障黄金法则

  1. trace 定位耗时,再 watch 验证数据
  2. 有流量用 watch,无流量用 ognl
  3. 热修改是临时方案,务必正式修复
  4. profiler 采样控制时长,避免影响线上性能

本文由千阶智能技术团队出品,转载请注明出处。更多技术内容请访问 qjchat.com

相关新闻
热点新闻
精彩视频
投票
查看结果
Tags

站点地图 在线访客: 今日访问量: 昨日访问量: 总访问量:

© 2026 北京千阶智能科技有限公司 版权所有

公安备案 京公网安备11010802049169号 京ICP备2026018665号-1