第9章 内存问题分析:dumpsys meminfo、procrank、内存泄漏、GC日志
内存问题,说白了就是Android系统里最让人头疼的“隐形杀手”。
我见过太多开发同学,代码跑得挺欢,一压测就崩。为什么?内存扛不住了。今天咱们就把这块硬骨头啃下来。我会从工具使用讲到实战排查,把我在项目中踩过的坑都抖出来。
9.1 先摸清家底:dumpsys meminfo
这是系统自带的内存诊断工具,不需要root,不需要额外安装。我个人习惯,遇到任何内存异常,第一件事就是跑这个命令。
adb shell dumpsys meminfo -a [包名]
输出信息量很大,我挑几个关键字段说:
| 字段 | 含义 | 我的关注点 |
|---|---|---|
| Native Heap | Native层堆内存 | 如果异常增长,多半是JNI泄漏 |
| Dalvik Heap | Java堆内存 | 重点关注Alloc和Free的差值 |
| PSS Total | 进程实际占用的物理内存 | 这个值最接近真实内存占用 |
| Views / Activities | 当前持有的View和Activity数量 | 如果Activity没释放,就是泄漏 |
9.2 更精细的视角:procrank
dumpsys meminfo看的是进程整体,procrank能深入到每个进程的细节。这个工具需要root权限,或者编译到userdebug版本里。
adb shell procrank
输出格式类似这样:
PID Vss Rss Pss Uss cmdline
1234 123456KB 45678KB 23456KB 12345KB com.example.app
这里我重点解释两个指标:
- PSS (Proportional Set Size):按比例分摊的共享库内存。这是系统判断内存压力的主要依据。
- USS (Unique Set Size):进程独占的物理内存。这个值最能反映你的应用到底吃了多少独享内存。
我曾经遇到一个案例:某个应用PSS只有80MB,但USS高达200MB。为什么?因为它fork了大量子进程,共享库被重复计算了。procrank一跑,问题一目了然。
9.3 内存泄漏:怎么找?怎么治?
内存泄漏,说白了就是对象该回收的时候没被回收。Java有GC,但GC不是万能的。只要还有引用链指向这个对象,GC就动不了它。
最常见的泄漏场景:
- 静态变量持有Activity引用:比如在Application里存了一个Activity的Context。
- 内部类隐式持有外部类引用:匿名Runnable里用了Activity的成员变量。
- Handler未及时移除:MessageQueue里还挂着你的Message,Activity已经finish了。
- 注册监听器未反注册:比如SensorManager、BroadcastReceiver。
实战排查步骤:
- 用dumpsys meminfo确认内存持续增长。
- 用adb shell dumpsys activity activities | grep "com.your.package" 看Activity数量。
- 抓取hprof文件:
adb shell am dumpheap [包名] /data/local/tmp/dump.hprof - 用MAT或Android Studio Profiler分析hprof,找GC Root。
9.4 GC日志:读懂系统的“心跳”
GC日志是系统垃圾回收的记录。你可以通过logcat看到它:
adb logcat -s "dalvikvm" // 低版本
adb logcat -s "art" // 高版本
典型的GC日志长这样:
I/art: Background partial concurrent mark sweep GC freed 2047(42KB) AllocSpace objects, 0(0B) LOS objects, 49% free, 2MB/4MB, paused 1.230ms total 117.217ms
我来拆解一下关键信息:
| 字段 | 含义 | 我的判断标准 |
|---|---|---|
| Background / Alloc | GC触发类型 | Background是后台GC,Alloc是分配失败触发的 |
| freed 2047(42KB) | 回收的对象数量和大小 | 如果每次回收都很少,说明内存碎片严重 |
| 49% free | 回收后堆空闲比例 | 低于20%就要警惕了 |
| paused 1.230ms | 暂停时间 | 超过10ms就会引起掉帧 |
你想想看,如果GC日志里频繁出现Alloc类型的GC,而且每次回收的内存很少,说明什么?说明堆内存快满了,而且碎片化严重。这时候就算总内存还有剩余,也分配不出连续的大对象。
9.5 知识体系总览
下面这张图是我自己总结的内存分析知识体系,你可以把它当作排查问题的路线图:
嗯,这张图基本把内存分析的流程串起来了。从上到下,先发现问题,再用工具定位,最后分析根因。每一步都有对应的手段,别跳步。
9.6 实战总结
最后,我把自己常用的排查步骤整理成清单:
- 第一步: 跑dumpsys meminfo,看PSS和Activity数量。连续跑三次,确认是否增长。
- 第二步: 如果有root权限,跑procrank看USS,确认是不是共享库的问题。
- 第三步: 观察GC日志,看GC频率和暂停时间。如果Alloc GC频繁,直接进入下一步。
- 第四步: 抓hprof,用MAT分析。重点看GC Root路径,找到泄漏点。
- 第五步: 修复后,重复第一步到第四步,确认问题解决。
记住一句话: 内存问题不可怕,可怕的是没有排查思路。工具就摆在那里,关键是你知不知道怎么看、怎么用。今天讲的这些,都是我这些年真金白银换来的经验,希望能帮你少走弯路。