引子:一个真实的排查场景
凌晨两点,运维群里炸了锅:"配置中心的变更没生效!部分用户刷出来的数据不对!"
我登录跳板机,连上 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 版本,命令如有变动请参考官方文档。)