☰
Arthas线上排查实战:sysprop定位乱码与heapdump分析内存泄漏
2026/10/22 11:57:53 网站建设 项目流程

我先说一个真实的排查场景:某天半夜,线上订单服务突然报出大量中文乱码,页面上的商品名称全变成问号。常规做法是翻日志、查代码提交记录、找运维重启碰运气,但我没有走这条老路。当时我做的第一件事,是远程登录线上机器,用 Arthas 执行了一条命令:sysprop file.encoding。几十毫秒后,结果直接告诉我这台 JVM 的默认编码被某个启动参数改成了 GBK,和上游调用方用的 UTF-8 完全对不上。乱码的根因,一分钟内就有了答案。

这就是我特别想跟你分享的:Arthas 这个 Java 线上诊断工具里的sysprop和heapdump两个命令,一个负责看 JVM 的运行环境参数,一个负责把内存堆快照完整导出来。前者适合定位环境相关、配置相关、编码相关的问题,后者适合定位内存泄漏、大对象、对象引用链层面的问题。这篇文章不打算把 Arthas 的所有命令都过一遍,而是聚焦这两个命令,把它们背后的原理、实操姿势、常见坑一次说透。适合正在学 Arthas 的 Java 工程师,也适合已经会用基本命令、但想知道更多线上排查细节的开发者。

1. 线上排查为什么需要 Arthas

1.1 传统排查方式的局限

先说说痛点。Java 应用进了生产环境之后,很多问题在开发环境根本复现不了:环境变量不同、依赖版本不同、流量模式不同、JVM 参数不同,任何一个差异都可能让线上行为跟本地完全不一致。传统排查路径大概有三条。

一是看日志。日志是标配,但很多关键信息根本不会进日志。比如一个静态 Map 为什么越积越大?某条线程为什么一直占着 CPU?这些日常不会打点记录,除非你提前写了很细的统计埋点。

二是加打印。加日志得改代码、走发布流程,这个时间成本在紧急故障面前几乎不可接受。就算你赶工加上去了,下一次故障未必就在同一个位置,你的打点也未必打在点子上。

三是重启碰运气。重启往往能短暂恢复,但问题会被打回原形,下一次故障还会再来,属于治标不治本。要是赶上重启过程特别慢,或者启动时有状态初始化,影响面还会进一步扩大。

这就要引出在线诊断工具的价值:它不修改代码、不重启进程,直接 attach 到正在运行的 JVM 里,读取运行时信息,甚至动态修改行为。Arthas 恰好是目前最成熟的这类工具之一,基础能力覆盖了线程、类加载、方法调用、内存、系统属性等各个维度,而且用法很接地气,不需要对方业务系统做任何改造。

1.2 sysprop 和 heapdump 在整个工具箱里的位置

Arthas 的命令很多,dashboard、thread、watch、sc、jad、trace,每个都有自己擅长的场景。刚开始接触时人容易乱:问题来了不知道先敲哪个命令。我的习惯是把它分成四类去记。

  • 看运行环境:sysprop 查系统属性,sysenv 查环境变量,vmoption 查 JVM 参数。
  • 看线程状态:thread 查线程栈,配合 dashboard 看 CPU 和内存概况。
  • 看类和方法:sc 查类加载情况,jad 反编译源码,watch 观察方法的入参出参和异常。
  • 看内存快照:heapdump 导出堆转储,配合 MAT 等工具分析对象引用。

sysprop 和 heapdump 正好落在两个完全不同的层级上。sysprop 是浅层的环境信息,毫秒级返回,适合先做排除法;heapdump 是深度的内存快照,耗时和开销都比较大,适合初步定位到和内存强相关的问题时再出手。我建议线上排障时先跑成本低的命令做筛查,真到需要导堆的时候再导堆,不要一上来就上重武器。

2. sysprop 命令实战:系统属性的一眼看穿

2.1 sysprop 能查什么:从 java.version 到 file.encoding

sysprop 本质上读取的是 JVM 的System.getProperties()。这个 Properties 对象里存放了 JVM 启动时的默认值,加上启动命令里用 -D 显式注入的参数。它和 Linux 的环境变量不是一回事,很多新人把这两个搞混,sysenv 看的才是 PATH、JAVA_HOME 那些环境变量。这里需要特别提醒,实战时一定分清。

sysprop 查的内容大致分四块:

分类常见属性
JVM 自身信息java.version、java.home、java.vm.version
运行时路径与文件user.dir、java.io.tmpdir、java.class.path
平台与本地化os.name、user.timezone、file.encoding、user.country
应用启动参数自定义的键值对

这些属性看着不起眼,却是很多疑难杂症的根源。举几个真实例子:user.timezone 被设置成 GMT,定时任务全部晚八小时;file.encoding 不一致,读到的文件变成乱码;java.io.tmpdir 指向不可写目录,临时文件创建失败。这些都是线上故障的常见诱因,而 sysprop 能在两分钟内把它们暴露出来。

2.2 sysprop 的实操步骤:查看、过滤、修改一条龙

连上 Arthas 之后,sysprop 的用法非常直接。

sysprop

不带参数,会列出当前 JVM 所有系统属性。输出格式是key = value,行数很多,配合 grep 过滤关键词使用会更顺手。

sysprop java.version

这个用法指定一个 key,只查看单个属性的值。Arthas 也支持按前缀过滤,比如执行sysprop java会返回所有以 java 开头的属性。实际用下来,它的过滤逻辑更接近包含匹配,比如sysprop file可以看到 file.encoding、file.separator 这一组属性。建议你连到一个测试环境试一次,输出的顺序和格式自己体会一下,比看文档快。

修改某个属性则用--update参数。

sysprop --update user.timezone Asia/Shanghai

执行后 Arthas 会返回修改后的新值,你可以在 JVM 里立即感受到变化。注意它只是临时改当前运行中的 JVM,进程重启后一切恢复原样。如果希望永久生效,得改启动脚本里的对应配置。

2.3 实战案例:定位乱码问题

回到开头说的乱码问题。当时线上订单服务突然一片乱码,上游系统用 UTF-8 编码报文字符串,下游收到的却是问号。我连上 Arthas 后,先敲了sysprop file.encoding,结果是 GBK。再敲sysprop sun.jnu.encoding,结果也是 GBK。这两项不一致意味着 JVM 内部用默认编码处理文本和文件名转换时,和上游完全对不上。

顺着线索翻启动脚本,发现有人在某次变更里加了-Dfile.encoding=GBK的配置。本意是想压缩日志文件体积,没想到整个 JVM 的默认编码全被带偏了。当时现场不能随便重启,我先用sysprop --update file.encoding UTF-8做了临时修正,新接收到的报文立刻恢复正常。等事后窗口期,通过正式发布流程把启动参数改回 UTF-8,彻底解决。

这个案例的关键点是:sysprop 不只是“查看”命令,它能在救急时“改”。但一定要牢记,运行期修改不改变启动配置,重启就失效,必须同步修正源头。如果你只查不改,当时只能干瞪眼等重启。

2.4 用 sysprop --update 临时修改:能救急,但要懂边界

救急的时候,--update确实好用,但有几个边界必须说清楚。

第一,它只对当前 JVM 生效,运行期可变参数很快失效。任何依赖该属性的已初始化组件,不一定能自动感知变化。

第二,很多系统属性改了并不是当场就生效。比如 file.encoding,如果某个类在启动阶段已经按旧编码初始化了 I/O 配置,那么运行期修改对已经存在的 Reader、Writer 是无效的,只对后续新建的流生效。所以改完之后要多留意是否真的有效果。

第三,不要用它修改 java.home、java.class.path 这类基础运行参数,这些参数在 JVM 启动时就已经决定了后续的行为,运行期改动会造成不可预期的问题。这部分属性可以查,但我不建议改。

我给出的建议是:先用 sysprop 查,查清楚后判断是不是属性层面的问题;如果真是,评估--update能不能在当前场景下立即见效,别盲目执行;有重启窗口的话,改配置后重启才是根治方案。

3. heapdump 命令实战:把整个堆搬到桌面上

3.1 heapdump 和内存分析的关系

内存问题大概是线上 JVM 最难缠的一类。CPU 高可以用 thread 快速定位,但“内存缓慢上涨”这种问题,既不会让进程立刻崩溃,也不会马上报错,只有到快要 OOM 时才引发连锁故障。要搞清楚哪些对象占着内存、被谁引用着,最直接的办法就是把整个堆导出来,离线分析。

heapdump 命令在 Arthas 里就是干这个的。它会把当前 JVM 的堆内存快照写入一个 .hprof 文件,这个文件可以被 MAT、VisualVM 等工具打开。后续的分析路径基本是:打开快照,看 Leak Suspects(疑似泄漏报告)或者 Histogram(对象类型直方图),找到占用空间异常大的对象,再顺着它的引用链往上看,是谁一直在持有这些对象。

稍微说下它的底层:Arthas 的 heapdump 实现原理是调用 HotSpot 的HotSpotDiagnosticMXBean的dumpHeap方法,这和jmap -dump:format=b,file=xxx的机制基本一致。所以你可以把它理解成一个不依赖 jmap 版本、不用单独敲命令的在线导出工具,而且操作起来更顺手。

3.2 触发一次堆导出:命令用法与输出解读

进入 Arthas 交互界面后,执行heapdump即可。

heapdump

默认在当前工作目录生成一个以 heapdump 开头的 .hprof 文件,文件名包含精确到秒的时间戳,避免互相覆盖。执行完成后,Arthas 会告诉你文件生成位置和文件大小。

如果想指定路径,直接带上路径参数。

heapdump /data/dumps/order-service.hprof

如果只需要存活对象,就用--live参数。

heapdump --live /data/dumps/order-service-live.hprof

--live参数的意思需要展开一下:它只保留从 GC Roots 出发可达的对象,也就是“还活着”的对象,同时把已死亡但尚未被回收的对象过滤掉。这样导出的文件体积会小很多,分析时也更聚焦。代价是执行时通常会触发一次 Full GC,在大堆场景下会带来明显的停顿。所以我在线上导堆,一般这样权衡:

  • 怀疑有大量可回收垃圾对象,想看清全貌,用不带--live的完整导出。
  • 只想聚焦存活对象里谁占空间大,用--live。
  • 堆特别大、又处在业务高峰期,先评估磁盘空间和 GC 影响,尽量挪到低峰期做。

3.3 用 MAT 分析 dump 文件:从对象引用链找到泄漏点

导出只是第一步,分析才是重头戏。我常用的工具是 Eclipse MAT,即 Memory Analyzer。用 MAT 打开 dump 文件时,第一次解析大文件会比较慢,机器的内存也要给足。如果你本地只有 4G 内存,硬开一个 8G 的 dump,大概率会卡死,我建议至少保证 2 到 4 倍的堆空间给分析工具。

MAT 打开后的标准动作是点 Leak Suspects。它会自动找出最可能内存泄漏的几组对象,并给出嫌疑报告。往下拉 Details,能看到每个嫌疑对象的 Retained Heap 大小、引用链 From Object 那一段,以及 GC Roots 的路径。比如某次我分析一个批处理服务,Leak Suspects 直接指出某个 HashMap 实例占用了超过 60% 的堆。顺着引用链点进去,发现是任务队列组件内部的去重缓存,key 是任务 ID,value 是整段任务报文,只加不删,任务量一大就爆了。

除了 Leak Suspects,Histogram 也很好用。它会按类名汇总所有实例的数量和占用空间。看到 char[]、String、byte[] 排在最前面是正常的,不一定是问题。真正要注意的是那些业务类对象,比如自定义的 TaskDetail、SessionWrapper 之类。如果实例数量巨大且 Retained Heap 很高,多半藏着问题,可以右键选择 List objects -> with outgoing references 继续往下钻取。

3.4 实操案例:一个缓慢增长的内存泄漏

说一个我亲测过的场景。某个报表生成服务,每天早上开始跑,跑到下午接口就明显变慢,每隔几天就 OOM 一次,重启后又能撑几天。表面看像是数据量大的问题,但实际老年代持续上涨,young 区回收也收不干净。

我连上 Arthas,先跑 dashboard 看内存概况,确认老年代一直在涨。然后执行 heapdump 导了一份快照,文件大约 2.3GB。把快照拉回本地,用 MAT 的 Leak Suspects 查看,嫌疑集中在某个 ArrayList 持有的对象引用链上。顺着引用链展开,发现是报表数据缓存对象没有被移除:每次生成完一份报表,结果对象就被丢进“最近结果缓存”集合,但集合的清理逻辑只处理了成功路径,异常退出时漏了 remove,导致缓存只进不出,最终占满老年代。

找到根因后修了一行代码,在 finally 块里补上清理逻辑,同时给缓存加了个最大容量限制,上线后老年代曲线就平稳了。这个案例是典型的“代码层面小 bug”引发“内存层面大故障”。如果没有 heapdump,光靠看代码靠猜,可能得折腾一整天;导出快照之后,半小时就把引用链理清楚了。

4. 两个命令协同使用与避坑经验

4.1 先看 sysprop 再决定要不要 heapdump

排障顺序上,我的经验是:能先用 sysprop 这类低开销命令确认的,就不要急着导堆。比如突然出现文件乱码、时区错乱、临时目录权限错误,这类问题的根因很可能就在系统属性层面,一条 sysprop 就出答案了。直接导堆反而会把人引到错误方向,白白浪费时间。

反过来,如果 sysprop、dashboard、thread 都看过了,问题仍指向某个业务对象占内存过多,或者老年代持续上涨,那就值得导出 heapdump。这两个命令的配合节奏可以这样掌握:

现象第一步第二步
异常现象带“环境”属性sysprop 排除编码、时区、路径、版本类问题必要时配合 sysenv、vmoption
异常现象带“性能”属性dashboard、thread 看 CPU、线程、内存概况锁定热点后再考虑是否深挖
内存指标持续上涨确认老年代趋势上 heapdump 做深度分析

这套顺序算不上什么黑科技,但它能帮你避免“一上来就抓重点、结果抓错重点”的尴尬。线上排障最怕的不是问题难,而是方向偏。工具再强大,方向偏了也是南辕北辙。

4.2 高频踩坑记录

用这两个命令时间长了,我总结了一些高频坑,每一条都是真金白银换来的经验。

第一个坑是 heapdump 导出的文件太大,服务器 /tmp 或 /data 分区被写满。有一次我导出前没看磁盘空间,一个 8GB 堆的进程直接生成了 6GB 的 dump 文件,写满了挂载点,导致服务日志瞬时打不进去,影响面反而比原来的内存问题还大。现在我的习惯是导出前先df -h确认剩余空间,预估 dump 文件大小约为堆大小的 60% 到 80%,留出足够冗余。

第二个坑是--live参数会触发 Full GC,在高峰期执行可能带来明显停顿。这个参数确实好用,能大幅缩小 dump 体积,但不要在核心交易链路的流量峰值时用。实在要用,可以先看 dashboard 确认当前 GC 频率,再决定要不要加--live。

第三个坑是 dump 文件包含敏感数据。堆转储里几乎保留了业务运行时所有的字符串数据,包括用户名、手机号、订单信息,甚至日志文本。把它从服务器拉回本地后,一定要做好权限管控。不往公共网盘丢,不随手发群,分析完及时删除。这是合规底线,不能只图方便。

4.3 我的经验笔记

最后整理一份简短的经验笔记,方便你之后直接参考。

  • sysprop 是查看 JVM 系统属性的第一工具,能发现问题,也能临时修正问题。
  • 修改系统属性前先确认影响范围,优先选重启后持久化的方案。
  • heapdump 导出前先看磁盘空间,预估文件大小,不要在峰值期做全量导出。
  • 分析 dump 用 MAT 的 Leak Suspects 和 Histogram 两个入口,按 80% 占比找大头对象。
  • dump 文件要当作敏感数据处理,管控好访问范围。
  • Arthas 的命令是协作式的,先用低开销命令做排除法,再决定要不要导堆。

这套方法我在多个模拟项目和真实故障中反复验证过,实战表现稳定。如果你只记住一句话,那就是:能看属性就别导堆,要导堆就做足准备。

说实话,Arthas 的命令有几十个,sysprop 和 heapdump 只是其中两个,但它们恰好覆盖了线上排障的第一层和最后一层:一个用毫秒级的速度回答“运行环境对不对”,一个用完整快照回答“内存里到底堆了什么”。我个人在实际操作中体会到,真正让排查变快的不是某个命令本身,而是你清楚什么时候该用哪个命令。建议你下次遇到线上问题时,先不要急着翻代码,先想想这个问题更接近环境配置层面的病,还是内存运行层面的病,然后让 sysprop 或者 heapdump 替你先跑一趟。用熟了之后,你会觉得线上问题并不可怕,可怕的是你没有一套高效的排查顺序。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询