内存分析入门:你要知道的那些事
做嵌入式或服务端开发的同学,几乎都遇到过程序跑着跑着就挂了——不是 OOM 就是卡死。我第一次接触内存分析,还是刚入职那年。项目经理扔过来一个 dump 文件,说“你看看哪个对象把堆撑爆了”。我当时连 dump 是什么都不太清楚,硬着头皮载进 MAT,看到一堆字符串占了几百 MB……后来发现就是个 HashMap 没清空,定期把日志往里塞。
从那以后我就明白了:内存分析不是锦上添花,而是活命技能。很多线上事故,表面是“响应慢”、“服务无响应”,根因都在内存上。你想想看,一个内存泄漏不到 1KB/s 的应用,跑上几天几周,GC 频率越来越高,最终 CPU 飙升,用户请求排大队——这种案例我见过不下十次。
什么是内存分析?
说白了,就是通过各种工具和手段,搞清楚进程里的内存被谁用了、用了多少、用在哪、有没有异常。比如:
- 哪些对象占的内存最多?
- 有没有不再需要却仍然被引用的对象?
- 堆空间是否被切得太碎导致无法分配大对象?
- 应用的内存增长曲线是否线性上升(泄漏嫌疑)?
我个人的习惯是:先看趋势,再看快照,最后深挖引用链。如果你一上来就盯着某个大对象不放,很可能被表象带偏。
实现层面,通常靠两种数据源:
- 堆转储(Heap Dump):某个时刻的内存快照,包含所有对象、类、引用关系。
- GC 日志:记录每次垃圾回收的耗时、回收量、堆容量变化。
char[] 和 String 这两个类型,它们往往是内存大户的藏身之处。
为什么需要内存分析?
我遇到过不少团队,线上问题全靠重启解决。按 Ctrl+C 重启后一切正常,但过几天又崩。嗯,这种“玄学”背后往往藏着内存泄漏。没有分析工具,你根本没法定位。
具体来说,内存分析能帮你解决这几类实际痛点:
| 场景 | 不分析的结果 | 分析后的收益 |
|---|---|---|
| 内存泄漏 | OOM、服务频繁重启 | 定位泄漏对象,精准修复 |
| 内存溢出 | 请求失败、进程杀死 | 调整参数或优化数据结构 |
| 内存碎片化 | 分配大对象失败、GC 频繁 | 改用池化或调整 GC 策略 |
| GC 频繁/耗时高 | 服务响应慢、CPU 满载 | 减少临时对象生成,降低压力 |
我在做一次金融系统压测时,发现服务每 5 分钟全 GC 一次,每次停顿 3 秒多。压测脚本里全是长连接 + 大并发,我们查了一周都没头绪。后来我打了两小时内的多个 dump,对比发现有个缓存类没设过期时间——所有请求的 session 对象全挂在 WeakHashMap 上,但 key 被强引用着,导致内存只进不出。修复后 GC 停顿降到 200ms 以下。这就是分析的威力。
常见内存问题概述
先画个总览图,让你对内存问题家族有个整体认识:
下面详细说说每种问题的症状和根因。
1. 内存泄漏
最头疼的,也是最常见的。泄漏不是一下子出问题,而是日积月累——对象已经没用了,但还有人引用着它,GC 没法回收。
典型场景:
- 使用了 ThreadLocal 但忘记 remove,线程池复用时数据越积越多。
- 注册了监听器/回调却未反注册。
- 动态加载类/ClassLoader 无法卸载,perm gen 泄漏(JDK 8 之前)。
list.clear()。
2. 内存溢出
简单粗暴——堆空间不够用了。可能是堆设置太小,也可能是某次请求分配了一个超大数组。
常见诱因:
- 一次性加载全部数据到内存(比如查数据库不分页)。
- 递归调用过深导致 StackOverflow(栈溢出)或堆分配爆炸。
- 大文件上传/大图片解码。
查看 JVM 参数 -Xmx 是最直接的排查起点。我见过有人把 -Xmx 设成 1GB,但 JVM 占用的物理内存却飙升到 2GB——那是因为还有 Direct Memory(NIO 缓冲区)和 Metaspace,这些都不算在堆里。
3. 内存碎片化
这个相对隐蔽。堆里还有很多空闲空间,但都是小得零碎的小块,连不成一块连续空间。当需要分配一个大对象(比如大数组)时,就会失败。
碎片化严重时,GC 次数增加但回收效果很差。现象是:堆占用不高,但老是 Full GC,而且每次 Full GC 后剩余空间没怎么变。
应对思路:
- 对对象规整的应用,使用 G1/GMS 等压缩式 GC。
- 减少大量小对象的频繁创建,使用对象池。
- 调整
-XX:MaxTenuringThreshold让对象尽早进老年代,减少年轻代晋升碎片。
好了,这三种问题就像内存领域的“三座大山”。它们经常交织在一起:泄漏导致可用空间减少,间接引发溢出;频繁分配释放又加剧碎片化。但别慌——后面的章节里,我会手把手教你用 MAT、jstack、jmap 这些工具一个一个把问题揪出来。
核心要点回顾
- 内存分析 = 通过堆转储 + GC 日志找出内存异常。
- 三大问题:泄漏(对象释放不掉)、溢出(空间不足)、碎片化(空间不连续)。
- 分析基本原则:先看趋势(GC 日志),再看快照(dump),最后追溯引用链。