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

一、观察方法耗时------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 内部https://zhida.zhihu.com/search?content_id=278942864&content_type=Article&match_order=2&q=%E8%B0%83%E7%94%A8%E8%B7%AF%E5%BE%84&zhida_source=entity
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.https://zhida.zhihu.com/search?content_id=278942864&content_type=Article&match_order=2&q=zaxxer&zhida_source=entity.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 实例变量

场景 获取方式 修改方式
有流量通过 watch watch + ognl 表达式
无流量通过 springbean + ognl springbean + 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 内存方法热点

复制代码
# 分析https://zhida.zhihu.com/search?content_id=278942864&content_type=Article&match_order=1&q=%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D&zhida_source=entity热点
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 5 Top 5 CPU 消耗线程
死锁检测 thread -b 查找死锁
性能采样 profiler start/stop CPU/内存热点分析

结语

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

相关推荐
神奇霸王龙2 小时前
Claude Code屠榜:MiMo与Grok紧追Codex
服务器·网络·人工智能·gpt·ai·ai编程
Web极客码2 小时前
WordPress SEO优化:提升网站排名的13个关键步骤
服务器·seo·wordpress
Ai_easygo4 小时前
MCP 协议实战:用 Claude Desktop 连本地 SQLite,5 分钟搭一个能查数据的 AI 助手
jvm·人工智能·sqlite
张小姐的猫5 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
国科安芯5 小时前
星间光链路:AS32S601型抗辐射MCU在空间激光通信终端控制中的技术实现
服务器·网络·单片机·嵌入式硬件·物联网·安全·信息与通信
EIP低代码平台5 小时前
EIP低代码平台系统-字典功能讲解
低代码·c#·工作流
吉普赛的歌7 小时前
YARP负载均衡配置了多个相同接口导致的报错
c#·yarp
imc.117 小时前
linux基础IO
linux·运维·服务器
躺不平的理查德7 小时前
让Ubuntu 的第二块网卡 ens37 永久固定为 192.168.30.111
运维·服务器