【死磕JVM】用Arthas揪出内存泄漏元凶:从监控到定位的实战指南

📅 2026/7/15 16:25:07 👁️ 阅读次数 📝 编程学习
【死磕JVM】用Arthas揪出内存泄漏元凶:从监控到定位的实战指南

1. Arthas简介与内存泄漏排查场景

第一次接触Arthas是在一个深夜的线上告警中——我们的订单服务内存占用突然飙升到90%。当时用jmap导出的堆转储文件足足有8GB,MAT分析到一半就直接OOM了。后来团队里的架构师扔给我这个阿里开源的诊断工具,我才发现原来排查JVM问题可以这么优雅。

Arthas最让我惊艳的是它的实时交互能力。不像传统工具需要停机分析,它能在应用运行时直接挂载诊断,通过命令行交互查看内存、线程、类加载等信息。特别是排查内存泄漏时,memory命令能实时观察堆内存变化,heapdump生成的文件也比jmap小很多。有次我仅用200MB的堆转储就定位到了ThreadLocal泄漏问题。

常见的内存泄漏场景主要有三种:

  • 集合类持有引用:比如全局的HashMap不断添加缓存对象
  • 线程池堆积任务:像我们遇到的订单处理线程池阻塞
  • 未关闭的资源:数据库连接、文件流等

2. 环境准备与问题复现

2.1 模拟内存泄漏案例

先准备一个故意制造泄漏的Demo(完整代码见GitHub):

public class MemoryLeakDemo { static List<byte[]> leakList = new ArrayList<>(); // 泄漏容器 public static void main(String[] args) throws Exception { while (true) { leakList.add(new byte[1024 * 1024]); // 每秒泄漏1MB Thread.sleep(1000); } } }

启动时加上内存限制参数:

java -Xms100m -Xmx100m MemoryLeakDemo

2.2 Arthas快速接入

下载并启动Arthas:

wget https://arthas.aliyun.com/arthas-boot.jar java -jar arthas-boot.jar

看到控制台输出目标进程时,输入对应编号即可挂载。我常用的是dashboard命令,它能实时显示:

Memory used total max usage heap 65M 256M 1024M 6.37% nonheap 45M 62M -1 72.58%

3. 实时内存监控与分析

3.1 memory命令深度使用

输入memory会显示关键内存区域数据:

Memory used total max usage heap 78.3MB 100.0MB 100.0MB 78.3% nonheap 35.2MB 56.0MB -1 62.8% codeheap 'non-nmethods' 3.4MB 5.0MB 68.0%

重点关注Old Gen区的增长趋势。如果持续上升不回落,很可能存在泄漏。我一般会配合-i参数定时采样:

memory -i 5000 # 每5秒刷新一次

3.2 堆内存快照生成

当发现Old Gen异常时,立即用heapdump保存现场:

heapdump /tmp/demo.hprof

这个命令比jmap更安全:

  1. 不需要指定进程ID
  2. 默认会进行压缩
  3. 支持只dump存活对象

4. 堆转储文件分析实战

4.1 MAT基础分析

用Eclipse Memory Analyzer打开dump文件,重点关注:

  1. Histogram:按类统计实例数
  2. Dominator Tree:显示对象引用链
  3. Leak Suspects:自动分析泄漏点

4.2 OQL查询技巧

在MAT中使用OQL定位大对象:

SELECT * FROM byte[] WHERE toString(runtimeClass).contains("MemoryLeakDemo")

这个语句能快速找到我们Demo中泄漏的byte数组。

5. 进阶内存诊断技巧

5.1 结合jvm命令

查看GC详细情况:

jvm -gc

输出示例:

G1 Young Generation: Eden Space: [0x00000000f0c00000,0x00000000f0c01000,0x00000000f0d00000) Survivor Space: [0x00000000f0d00000,0x00000000f0d00000,0x00000000f0e00000)

5.2 监控特定类

用sc命令查看类加载信息:

sc -d *MemoryLeakDemo

输出会显示类加载器、来源JAR包等关键信息。

6. 内存问题修复与验证

找到泄漏点后,常见的修复方式有:

  1. 修复集合引用:改用WeakHashMap
  2. 清理线程局部变量:及时remove() ThreadLocal
  3. 资源关闭:实现AutoCloseable接口

修复后可以用jmap -histo:live验证对象是否被回收。

记得有次修复一个缓存泄漏后,堆内存从4G直接降到800MB,那种成就感比写完十万行代码还爽。建议每次修复后持续监控24小时,有些泄漏是定时任务触发的。

7. 生产环境注意事项

  1. 安全限制:用--target-ip限制访问IP
  2. 性能影响:heapdump时暂停线程,避开高峰
  3. 权限控制:禁止普通用户执行高危命令

推荐在预发环境充分测试,我用Arthas排查过的线上问题,从没因为工具本身引发故障。