24、Profile Heap与Allocation Tracker:Android Studio Profiler使用,内存快照对比

说到Android内存分析,Android Studio Profiler绝对是我们最趁手的兵器。我个人习惯是,只要遇到内存异常增长或者OOM,第一反应就是打开Profiler看看。今天咱们就聊聊里面两个最核心的功能:Profile Heap和Allocation Tracker。

Profile Heap:内存快照的“体检报告”

Profile Heap说白了,就是给Java堆拍一张X光片。它能告诉你当前堆里有哪些对象,各占多少内存。我刚开始用的时候,总觉得这玩意儿就是一堆数字,后来才发现,关键是要学会“对比”。

核心思路:内存泄漏的本质,就是本该被回收的对象,因为被某个GC Root引用着,一直赖在堆里不走。Profile Heap能帮你把这些“钉子户”揪出来。

怎么拍快照?

操作其实很简单:

  1. 打开Android Studio,连上设备或模拟器
  2. 点击底部的“Profiler”标签
  3. 选择你的应用进程
  4. 在Memory区域,点击“Dump Java Heap”按钮(就是那个小垃圾桶图标旁边的)

嗯,这里要注意:拍快照的时候,最好让App处于一个“稳定状态”。比如你刚从一个页面返回,等个两三秒再拍。为什么?因为GC可能还没跑完,你拍到的数据是“半成品”,容易误导。

快照里看什么?

拍完之后,你会看到一个列表。我一般先看这几个指标:

指标 含义 我的关注点
Allocations 该类对象的实例数量 数量异常多的类,比如Activity
Native Size Native层占用的内存 Bitmap、OpenGL相关对象
Shallow Size 对象本身占用的内存 单个对象的大小
Retained Size 对象及其引用的所有对象的总大小 这个最有用,能看出“如果回收这个对象,能释放多少内存”

举个例子,我曾经排查过一个图片列表页面的内存泄漏。拍快照后发现,MainActivity的实例数量是3,但理论上应该只有1个。点进去一看,有个匿名内部类持有了Activity的引用,导致页面关闭后Activity无法回收。这就是典型的泄漏场景。

Allocation Tracker:追踪对象的“出生记录”

Profile Heap告诉你“谁在内存里”,而Allocation Tracker告诉你“谁把它生出来的”。

说白了,Allocation Tracker能记录每个对象是在哪一行代码里new出来的。这个功能在定位“谁在频繁创建对象”时特别好用。

怎么用?

操作流程:

  1. 在Profiler的Memory区域,点击“Record”按钮
  2. 在App里执行你想要分析的操作(比如滑动列表、点击按钮)
  3. 点击“Stop”结束录制

录制结束后,你会看到一个时间线,上面密密麻麻全是分配记录。别慌,我教你一招:按“Allocation Size”排序,从大到小看。那些一次分配几百KB甚至几MB的对象,十有八九是Bitmap或者byte[]。

我的小技巧:录制时间不要太长,5-10秒就够了。时间太长数据量太大,反而不好分析。另外,录制前先手动触发一次GC,让堆干净一点,这样录到的数据更有针对性。

内存快照对比:两张X光片找差异

单看一张快照,很多时候看不出问题。真正厉害的是“对比”。

我常用的对比方法:

  • 操作前后对比:进入某个页面之前拍一张,返回之后拍一张。如果返回后某些对象还在,而且数量没减少,那就有问题了。
  • 多次操作对比:同一个操作重复做3次,每次操作后拍一张。如果某个类的实例数量随着操作次数线性增长,那基本可以确定是泄漏。

举个例子,我曾经排查过一个“返回后内存不降”的问题。操作前快照里MainActivity有1个,操作后快照里MainActivity有2个。点进去看引用链,发现是一个静态的Callback持有了Activity的引用。嗯,典型的“注册了没注销”。

SVG:内存分析流程概览

Android Studio Profiler 内存分析流程 第一步:发现问题 内存增长 / OOM / 卡顿 第二步:Profile Heap Dump Java Heap 拍快照 第三步:分析快照 查看Retained Size 发现可疑对象? 第四步:Allocation Tracker 录制分配记录,定位代码行 第五步:修复验证 修改代码 → 再次Profile Heap对比 否,继续观察 核心:快照对比 → 发现泄漏 → Allocation Tracker定位 → 修复验证

实战:一次完整的排查过程

我记得有一次,一个社交App的“朋友圈”页面,用户反馈滑动越来越卡。我打开Profiler一看,内存一直在涨,从不回落。

我的排查步骤:

  1. 拍第一张快照:刚进入页面时拍一张
  2. 反复滑动:上下滑动10次
  3. 拍第二张快照:滑动结束后拍一张
  4. 对比:发现FeedItemViewHolder的数量从5变成了15,而且没有减少
  5. 看引用链:发现ViewHolder被一个全局的ImageLoader缓存持有了
  6. 修复:在页面销毁时清空缓存

修复后再拍快照对比,ViewHolder的数量在返回后归零了。问题解决。

注意:不要只看Shallow Size,那只是对象本身的大小。Retained Size才是“如果干掉这个对象,能释放多少内存”。我见过有人盯着一个只有几十字节的对象纠结半天,结果它背后引用着一个几MB的Bitmap。

一些实用技巧

  • 过滤系统类:在Profiler的类列表里,可以勾选“Show only classes from my app”,这样能过滤掉系统类,聚焦在自己的代码上。
  • 使用正则搜索:在搜索框里输入“Activity$”可以快速找到所有Activity的子类。
  • 导出快照:点击快照右上角的“Export”按钮,可以导出为.hprof文件,方便分享给同事分析。

嗯,说到底,Profile Heap和Allocation Tracker就像医生的听诊器和CT机。听诊器(Profile Heap)能快速判断有没有问题,CT机(Allocation Tracker)能精确定位病灶在哪。两者配合使用,才能高效地解决内存问题。

我曾经带过一个新人,他每次排查内存问题都只盯着Allocation Tracker看,结果被海量的分配记录淹没了。我告诉他:先拍快照,找到可疑对象,再用Allocation Tracker去定位它的出生点。顺序搞对了,效率能翻倍。


公众号:蓝海资料掘金营,微信deep3321