Android Perfetto 完整内存分析图文教程(含操作截图说明+实战案例+界面标注)
一、整体架构与工具对比(先理清什么时候用Perfetto)
1.1 三种内存工具适用场景
| 工具 | 优点 | 缺点 | 内存问题定位 |
|---|---|---|---|
| Perfetto | Java+Native双堆调用栈、时序联动CPU/GC/Binder/卡顿、长录制、火焰图、SQL统计、可自动化脚本 | heapprofd堆快照需要Root | Native泄漏、Bitmap暴涨、匿名内存泄漏、OOM根因、内存抖动 |
| Android Studio Profiler | 可视化简单、无需Root、一键抓取 | Native追踪弱、大录制易崩、无法看malloc调用栈 | 日常粗略观察内存波动 |
| MAT | Java对象引用链、静态泄漏、Activity残留分析极强 | 完全看不到Native层内存 | Java纯堆内存泄漏 |
1.2 两个核心内存数据源(截图里会对应看到)
- android.mem(内存计数器):无需Root,看PSS/RSS/Dalvik堆/Native堆/匿名内存整体曲线,判断是否持续上涨(泄漏判定依据)
- heapprofd堆剖析器:Android 9+,需要Root,抓取malloc/new分配调用栈,生成火焰图,精准定位哪行代码未释放内存
1.3 环境准备(操作前置)
- 手机开启:开发者选项 → USB调试
- Root设备执行(模拟器直接生效)
bash
adb root
adb shell setprop persist.traced.enable 1
二、第一步:网页端可视化抓取(带界面截图标注步骤)
2.1 进入录制页面截图说明
- 打开 ui.perfetto.dev → 左上角 RECORD NEW TRACE
界面区域拆分:
- 左侧:Data Sources 数据源勾选区
- 中上:设备选择、录制时长、Buffer缓冲区大小
- 中下:过滤指定App包名
2.2 内存必勾选数据源(截图勾选框对应项)
在 Data Sources 中勾选:
【必选内存】
- Memory → ✅ Memory counters(整体内存趋势曲线,所有人都能抓)
- Memory → ✅ Native heap profiling (heapprofd)(Root才能生效,堆分配快照)
【辅助联动排查(建议一起勾)】
- CPU → CPU Scheduling(CPU占用,判断内存抖动是否伴随大量GC)
- ART → Java GC Events(GC触发点,定位频繁GC内存抖动)
- App → Atrace apps(页面生命周期,Activity销毁节点对应内存是否回落)
2.3 过滤目标App(关键)
Package name filter 输入应用包名,例如:com.xxx.memleakdemo
Recording duration:推荐 60s~120s
Buffer size:64MB(避免trace截断丢失数据)
2.4 开始录制+复现问题
- 点击 ADD ADB DEVICE 选择已adb连接手机
- 点 START RECORDING 开始录制
- 手机端重复复现操作:反复打开关闭页面、滑动列表、加载大图、播放视频
- 倒计时结束自动下载
.perfetto-trace文件,拖拽进网页打开
三、第二步:Trace界面逐层解读(每一块对应截图视图说明)
3.1 顶层:内存时序轨道(判断是否泄漏,最直观)
打开trace后,左侧时间轴找到你的App进程,展开 Memory 轨道:
截图上5条核心曲线含义
- PSS(最重要)
- 正常:操作后页面销毁+GC,内存下降,波浪起伏
- 泄漏:每一次操作阶梯式上涨,永不回落,斜线持续走高 → 实锤内存泄漏
- Dalvik Heap:Java堆(对象、数组、字符串、静态变量)
- Native Heap:C++ malloc、Bitmap像素缓冲区、FFmpeg、OpenGL、第三方SO库内存(7.0后Bitmap存在这里,MAT看不到)
- Anonymous RSS 匿名内存:mmap内存映射、内存池、帧缓冲区,很多OOM元凶
- Stack / Code:线程栈、dex/so代码段,一般不会大幅波动
配套GC轨道联动查看
下方 Android ART GC 彩色竖线:频繁密集竖线 = 内存抖动,大量临时对象循环创建未回收。
3.2 核心:Heap Profile 堆快照火焰图(定位泄漏代码,截图重点)
1. 找到dump标记
时间轴上 黑色菱形小图标 = heapprofd手动/自动堆dump点,点击菱形弹窗打开火焰图。
2. 两种视图切换(UI右上角下拉框)
- Size 总分配大小:找占用内存最大的代码路径(内存大户)
- Unreleased Size 未释放大小【泄漏专用】:只统计分配后没有free的内存,直接定位泄漏点(排查泄漏永远选这个)
3. 火焰图阅读规则(看图方法)
- 纵向:调用栈从上到下(父方法→子方法)
- 横向宽度:占用内存体积越大,柱子越宽
- 点击某一层调用栈 → 展开完整类名/方法名/Native SO函数名
- 右键
Focus:只聚焦当前调用栈,复制方法名全局搜索代码
截图常见两种泄漏栈样式
样式A:Java层泄漏栈
com.xxx.MainActivity
└── static ListenerHolder
└── Activity实例被静态强引用 未释放
样式B:Native Bitmap泄漏栈
libandroid_runtime.so
└── BitmapFactory.decodeFile
└── Glide加载大图未recycle、列表未复用
样式C:自己SO库内存泄漏
libxxx_encoder.so
└── malloc
└── C++ 解码buffer未free/delete[]
3.3 UI实用操作小技巧(截图按钮位置)
- 顶部搜索框输入包名,过滤只显示目标进程
- 右侧
Metrics→ 查看内存增量统计表格 - 底部
Query(SQL)执行SQL做批量量化分析
四、三个可直接复现的实战案例(带现象+Perfetto截图表现+修复)
案例1:Java静态变量持有Activity泄漏(最常见)
问题代码
java
public class GlobalListener {
// 静态变量强引用页面,onDestroy无法回收
public static View.OnClickListener sClick;
}
Perfetto Trace截图表现
- Dalvik Heap曲线:反复开关页面持续上涨,退出页面不回落
- Heap火焰图Unreleased Size:顶层直接指向
DetailActivity被静态类持有
修复
- onDestroy() 置空静态引用
- WeakReference弱引用包装回调
- LeakCanary二次确认引用链
案例2:大图Bitmap Native内存泄漏(Android高频坑)
现象
瀑布流无限滑动,Native Heap直线飙升,最终OOM崩溃,MAT查不到任何问题。
Perfetto截图特征
- Native堆曲线陡峭上涨,Dalvik堆变化很小
- 堆快照火焰图大量
BitmapFactory.nativeDecode未释放栈
原因
Android 7.0+ Bitmap像素存在Native堆;列表Item不复用、大图未压缩、未调用recycle()
修复
- Glide/Coil设置采样率 inSampleSize
- 页面销毁遍历ImageView recycle Bitmap
- 开启图片内存复用inBitmap
案例3:自研C++ so malloc忘记free Native泄漏
现象
打开视频解码页退出后,Native内存只涨不降。
Perfetto火焰图截图特征
栈直接指向 libmedia_codec.so 内部malloc函数,带C++函数符号
修复
Java层onDestroy调用native_release(),内部补全free(buffer)
五、命令行一键抓取脚本(替代网页手动点,适合自动化)
5.1 heap_profile官方脚本(推荐抓堆)
Mac/Linux
bash
# 下载脚本
curl -LO https://raw.githubusercontent.com/google/perfetto/main/tools/heap_profile
# 录制60秒堆快照,指定包名
python3 heap_profile android -n com.xxx.memleakdemo --duration 60s -o mem_leak.perfetto-trace
5.2 adb完整perfetto命令(全维度内存+GC+堆)
bash
adb shell perfetto -o /data/misc/perfetto-traces/mem_full.trace -t 90s -a --txt <<EOF
buffers { size_kb: 65536 fill_policy: DISCARD }
data_sources { name: "android.mem" }
data_sources { name: "android.gc" }
data_sources { name: "linux.process_stats" }
data_sources {
name: "android.heapprofd"
heapprofd_config {
process_cmdline: "com.xxx.memleakdemo"
sampling_interval_bytes: 1024
continuous_dump_config { dump_interval_ms: 5000 }
}
}
EOF
# 拉取到电脑
adb pull /data/misc/perfetto-traces/mem_full.trace ./
六、高阶:Perfetto SQL 查询(UI底部Query面板直接执行)
1. 查询App最大PSS内存
sql
SELECT process_name, MAX(value) max_pss_kb
FROM counter
WHERE name = 'mem.pss'
GROUP BY process_name
ORDER BY max_pss_kb DESC
LIMIT 10;
2. Top20未释放Native泄漏调用栈
sql
SELECT name, SUM(unreleased_size) leak_bytes
FROM heap_profile_allocation
GROUP BY name
ORDER BY leak_bytes DESC
LIMIT 20;
七、所有截图点位汇总(你打开网页后对应找即可)
- 录制页截图点:Record new trace → Data Sources勾选框 → Package过滤输入框
- Trace总览截图点:进程Memory轨道PSS/Native/Dalvik三条曲线
- GC截图点:ART GC密集竖线(内存抖动)
- Heap Dump截图点:时间轴黑色菱形标记
- 火焰图截图点:Unreleased Size视图、调用栈层级、Focus过滤按钮
- SQL截图点:底部Query输入框执行结果表格
八、避坑总结(踩过的坑)
- heapprofd堆快照必须Root,非Root只能看内存曲线,不能拿调用栈
- Release包代码混淆,火焰图是a.b.c类名,需要上传mapping.txt解析符号
- 采样间隔1024字节够用,改成1字节精度最高但trace文件巨大
- 不要把"内存占用高"等同于泄漏,页面销毁内存不回落才是泄漏
- 匿名内存暴涨优先排查mmap、OpenGL纹理、音视频缓冲区
如果你需要,我可以:
- 给你每一步对应的AI生成标注版高清示意图;
- 或者给一份Windows批处理一键抓内存泄漏脚本。