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 版本,命令如有变动请参考官方文档。)

相关推荐
程序员梅雨15 小时前
【从零开始学习数据仓库 | 第三篇】: Flume——数据采集基石
java·大数据·数据仓库·后端·学习·flume
acd1200915 小时前
域名解析缓存带来的隐性故障,很多排查都漏掉了这一层
java·运维·nginx
wuminyu15 小时前
JVM执行引擎学习总结:热点字节码编译成本地机器码
java·linux·c语言·jvm·c++
真上帝的左手15 小时前
2. 编程语言-Java-Spring Boot-JAR
java·spring boot·jar
Jack_hsfs15 小时前
第6章Spring 管理的是对象,还是对象的说明书?
java·后端·spring
十二同学啊15 小时前
Python 工程化详解:项目结构、依赖管理、代码质量、测试与部署
java·开发语言·python
YIN_尹15 小时前
【git原理与使用】git基本操作及底层原理(一)
java·git·elasticsearch
程序员飞哥15 小时前
AI 编程额度怎么用才不浪费?
java·前端·后端
yh弓长15 小时前
Spring MVC 的请求链路
java·spring·mvc