Arthas 实战:线上“看 Map”的四种武器

引子:一个真实的排查场景

凌晨两点,运维群里炸了锅:"配置中心的变更没生效!部分用户刷出来的数据不对!"

我登录跳板机,连上 Arthas,试图看一眼那个传说中的 domainHotConfigMap------一个存着所有域名配置的缓存 Map。按照惯例,我敲下:

bash 复制代码
watch com.example.HotConfigService getConfigMap 'returnObj' -x 3

然后等着请求触发。结果等了五分钟,这个方法压根没人调用------凌晨两点,哪来的流量?

这时候我才意识到:我需要一种不依赖请求就能看到 Map 内容的方法。

这就是本文要讲的主题:在 Arthas 中,到底有几种方式可以查看 Map 的值?各自适用于什么场景?


为什么"看 Map"是线上排查的高频场景?

在 Java 后端系统里,Map 几乎是无处不在的数据结构:

场景 Map 的角色
本地缓存 存热点数据,减少 RPC 调用
配置中心客户端 存拉取下来的配置键值对
Session / 上下文 存请求维度的临时数据
聚合统计 存计数、求和等中间结果
静态常量池 存枚举映射、错误码映射

这些 Map 出问题时,症状往往是:数据不对、数据缺失、数据过多、数据不更新。而最尴尬的是,这些 Map 通常没有对外暴露的查询接口,日志里也不会把全部内容打出来。

Arthas 提供了 4 种"看 Map"的手段,分别对应不同的场景。下面逐一拆解。


武器一:getstatic --- 查看静态 Map

适用场景:Map 是 static 修饰的类属性,比如全局缓存、常量映射表。

命令格式:

bash 复制代码
getstatic 类名 字段名 -x 展开深度

实战示例:

bash 复制代码
getstatic com.example.CacheManager globalCache -x 3

按 Key 过滤(借助 OGNL):

bash 复制代码
getstatic com.example.CacheManager globalCache 'entrySet().iterator.{? #this.key=="userId_123"}' -x 3

优点 :无需请求触发,即时查看。

局限:只能看静态字段,普通实例字段无能为力。


武器二:watch --- 观测方法执行时的 Map

适用场景:想看在某个方法入参 或返回值中出现的 Map。

命令格式:

bash 复制代码
watch 类名 方法名 '观察表达式' -x 展开深度

查看返回值 Map:

bash 复制代码
watch com.example.OrderService getOrderMap 'returnObj' -x 3

同时查看入参和返回值:

bash 复制代码
watch com.example.OrderService getOrderMap '{params[0], returnObj}' -x 3

带条件过滤(只关心特定参数):

bash 复制代码
watch com.example.OrderService getOrderMap 'returnObj' 'params[0]=="vip_user"' -x 3

优点 :能看到方法执行瞬间的 Map 快照,可以同时看入参和返回值做对比。

局限 :必须等待方法被调用才能触发输出,无流量时无法使用。


武器三:vmtool --- 无请求直接查堆内存

适用场景:最强大的武器。不管 Map 是静态还是实例属性,只要对象在堆里活着,就能直接捞出来看。

从 Arthas 3.5.1 开始支持,底层通过 JVMTI 接口直接查询堆中的对象实例。

查看某个 Bean 的实例属性 Map:

bash 复制代码
vmtool --action getInstances --className com.example.HotConfigService --express 'instances[0].domainConfigMap' -x 3

只看 Map 大小(防止数据太多刷屏):

bash 复制代码
vmtool --action getInstances --className com.example.HotConfigService --express 'instances[0].domainConfigMap.size()' -x 1

按 Key 查找:

bash 复制代码
vmtool --action getInstances --className com.example.HotConfigService --express 'instances[0].domainConfigMap.get("example.com")' -x 3

查看所有 Key:

bash 复制代码
vmtool --action getInstances --className com.example.HotConfigService --express 'instances[0].domainConfigMap.keySet()' -x 2

优点 :不依赖请求,直接查堆,最通用。

注意 :instances[0] 假设是单例;如果类有多个实例,先用 instances.length 确认。


武器四:ognl --- 终极灵活但略复杂

适用场景:vmtool 和 getstatic 覆盖不到的特殊情况,或者需要执行更复杂的表达式。

执行静态方法或读取静态字段:

bash 复制代码
ognl '@com.example.CacheManager@getCacheMap()'

执行任意表达式:

bash 复制代码
ognl 'new java.util.HashMap().put("key", "value")'

优点 :理论上什么都能做。

局限:需要熟悉 OGNL 语法,对新手不太友好。


三种主流武器对比

命令 适用场景 是否依赖请求 是否支持实例字段 上手难度
getstatic 静态 Map 属性 ❌ 不依赖 ❌ 不支持 ⭐ 简单
watch 方法入参/返回值的 Map ✅ 依赖 ✅ 支持 ⭐⭐ 中等
vmtool 任意对象的 Map 属性 ❌ 不依赖 ✅ 支持 ⭐⭐ 中等
ognl 复杂表达式 / 特殊场景 ❌ 不依赖 ✅ 支持 ⭐⭐⭐ 较难

实战心得

1. 最佳实践:vmtool 优先

如果你在排查一个缓存 Map 的内容不对 ,不知道这个 Map 什么时候会被更新,也不知道哪个方法会触发它------直接用 vmtool,一锤定音。

2. 带上 -x 参数

Arthas 默认的展开深度是 1,对于嵌套的 Map 结构,经常看到的是 {} 或引用地址。务必加上 -x 3 甚至 -x 5。

3. 如果 Map 是 null

有时候取出来是 null,不一定是真的没数据,可能是懒加载。试试调用 getter 方法:

bash 复制代码
vmtool --action getInstances --className com.example.HotConfigService --express 'instances[0].getDomainConfigMap()' -x 3

4. 确认类加载器

如果出现 ClassNotFoundException,可能是类加载器不对。先用 sc -d 查类信息,拿到类加载器哈希,然后在 vmtool 后面加上 -c 类加载器哈希。

5. 实战示例(来自真实场景)

某次排查中,我需要查看 LoadService 里的 configMap 内容:

bash 复制代码
vmtool -x 3 --action getInstances --className LoadService --express 'instances[0].configMap' -c 9f1388f9

输出结果:

复制代码
@ConcurrentHashMap[
    @String(
        key=@String['value'],
        oldValue=null,
        newValue=@String['value']
    )
]

从 oldValue=null, newValue="value" 可以看出这是一条新增的配置记录,而不是修改,帮助定位了配置加载的逻辑问题。


写在最后

Arthas 的命令很多,但对于"看 Map"这个需求,核心记住三句话:

有请求看方法 → 用 watch

静态缓存 → 用 getstatic

其他所有情况 → 用 vmtool

掌握了这三个命令,线上 Map 相关的数据问题基本可以做到"手到擒来",不用再靠猜、靠加日志、靠重新发布。

最后送大家一句排查口诀:

本地缓存不更新,vmtool 直接捞数据;

方法返回不对劲,watch 抓包看现场;

静态常量映射表,getstatic 查分明。

希望这篇文章能帮你在下次线上 Map 出问题时,少熬夜,多睡觉。


(本文基于 Arthas 3.7.x 版本,命令如有变动请参考官方文档。)

相关推荐
badhope10 分钟前
Spring AI Alibaba停更:Java程序员的信任,还能被辜负几次?
java·spring
const_qiu17 分钟前
第一章 1.1-1.2:基础模型调用与 TypeScript 入门 —— Java 开发者的视角
java·javascript·typescript
Wang's Blog22 分钟前
Java 项目实战: 交友项目 - Dubbo 负载均衡策略与实战验证
java·dubbo·交友
SL_staff26 分钟前
销售知识库7天落地实践:面向业务侧的零代码嵌入方案
java·开源·产品
GreatSQL社区36 分钟前
主从复制SQL线程中断——字符集砸出的坑
java·数据库·算法
Wang's Blog1 小时前
Java 项目实战: 交友项目 - 入门案例:服务提供者搭建总结
java·交友
hudd6681 小时前
JavaEE 初阶 - 多线程(进阶)
java·开发语言·java-ee
Pniubi1 小时前
区分Java创建线程池、多线程
java·线程·线程池·多线程·并发
程序员梅雨1 小时前
ZooKeeper面经总结
java·大数据·linux·分布式·后端·zookeeper·中间件
Yyyyyy~2 小时前
【Java】多线程初阶
java