第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没释放,就是泄漏
我的小技巧: 连续跑三次dumpsys meminfo,看PSS Total是否持续增长。如果每次增加几十KB,基本可以断定有内存泄漏。

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就动不了它。

最常见的泄漏场景:

  1. 静态变量持有Activity引用:比如在Application里存了一个Activity的Context。
  2. 内部类隐式持有外部类引用:匿名Runnable里用了Activity的成员变量。
  3. Handler未及时移除:MessageQueue里还挂着你的Message,Activity已经finish了。
  4. 注册监听器未反注册:比如SensorManager、BroadcastReceiver。

实战排查步骤:

  1. 用dumpsys meminfo确认内存持续增长。
  2. 用adb shell dumpsys activity activities | grep "com.your.package" 看Activity数量。
  3. 抓取hprof文件:adb shell am dumpheap [包名] /data/local/tmp/dump.hprof
  4. 用MAT或Android Studio Profiler分析hprof,找GC Root。
我曾经踩过的坑: 有一次排查内存泄漏,hprof文件抓了十几次,MAT分析都说没问题。后来才发现,我抓hprof的时候应用已经处于低内存状态,系统主动回收了一部分对象。所以,抓hprof一定要在内存还在增长的时候抓,别等OOM了再抓。

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,而且每次回收的内存很少,说明什么?说明堆内存快满了,而且碎片化严重。这时候就算总内存还有剩余,也分配不出连续的大对象。

我的建议: 在应用启动后,连续观察30秒的GC日志。如果GC间隔越来越短,或者每次GC的暂停时间越来越长,基本可以断定内存有问题。这时候别犹豫,直接上hprof分析。

9.5 知识体系总览

下面这张图是我自己总结的内存分析知识体系,你可以把它当作排查问题的路线图:

Android内存问题分析知识体系 问题发现 工具定位 根因分析 现象观察 • 卡顿/掉帧 • 频繁GC • OOM崩溃 工具链 • dumpsys meminfo • procrank • hprof + MAT 泄漏类型 • 静态引用 • 内部类 • Handler 解决方案:修复引用链 → 优化内存分配 → 监控GC日志 核心原则:先观察现象,再定位工具,最后分析根因

嗯,这张图基本把内存分析的流程串起来了。从上到下,先发现问题,再用工具定位,最后分析根因。每一步都有对应的手段,别跳步。

9.6 实战总结

最后,我把自己常用的排查步骤整理成清单:

  1. 第一步: 跑dumpsys meminfo,看PSS和Activity数量。连续跑三次,确认是否增长。
  2. 第二步: 如果有root权限,跑procrank看USS,确认是不是共享库的问题。
  3. 第三步: 观察GC日志,看GC频率和暂停时间。如果Alloc GC频繁,直接进入下一步。
  4. 第四步: 抓hprof,用MAT分析。重点看GC Root路径,找到泄漏点。
  5. 第五步: 修复后,重复第一步到第四步,确认问题解决。

记住一句话: 内存问题不可怕,可怕的是没有排查思路。工具就摆在那里,关键是你知不知道怎么看、怎么用。今天讲的这些,都是我这些年真金白银换来的经验,希望能帮你少走弯路。


专注资料整理