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能帮你把这些“钉子户”揪出来。
怎么拍快照?
操作其实很简单:
- 打开Android Studio,连上设备或模拟器
- 点击底部的“Profiler”标签
- 选择你的应用进程
- 在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出来的。这个功能在定位“谁在频繁创建对象”时特别好用。
怎么用?
操作流程:
- 在Profiler的Memory区域,点击“Record”按钮
- 在App里执行你想要分析的操作(比如滑动列表、点击按钮)
- 点击“Stop”结束录制
录制结束后,你会看到一个时间线,上面密密麻麻全是分配记录。别慌,我教你一招:按“Allocation Size”排序,从大到小看。那些一次分配几百KB甚至几MB的对象,十有八九是Bitmap或者byte[]。
我的小技巧:录制时间不要太长,5-10秒就够了。时间太长数据量太大,反而不好分析。另外,录制前先手动触发一次GC,让堆干净一点,这样录到的数据更有针对性。
内存快照对比:两张X光片找差异
单看一张快照,很多时候看不出问题。真正厉害的是“对比”。
我常用的对比方法:
- 操作前后对比:进入某个页面之前拍一张,返回之后拍一张。如果返回后某些对象还在,而且数量没减少,那就有问题了。
- 多次操作对比:同一个操作重复做3次,每次操作后拍一张。如果某个类的实例数量随着操作次数线性增长,那基本可以确定是泄漏。
举个例子,我曾经排查过一个“返回后内存不降”的问题。操作前快照里MainActivity有1个,操作后快照里MainActivity有2个。点进去看引用链,发现是一个静态的Callback持有了Activity的引用。嗯,典型的“注册了没注销”。
SVG:内存分析流程概览
实战:一次完整的排查过程
我记得有一次,一个社交App的“朋友圈”页面,用户反馈滑动越来越卡。我打开Profiler一看,内存一直在涨,从不回落。
我的排查步骤:
- 拍第一张快照:刚进入页面时拍一张
- 反复滑动:上下滑动10次
- 拍第二张快照:滑动结束后拍一张
- 对比:发现FeedItemViewHolder的数量从5变成了15,而且没有减少
- 看引用链:发现ViewHolder被一个全局的ImageLoader缓存持有了
- 修复:在页面销毁时清空缓存
修复后再拍快照对比,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