android - 性能 - Perfetto - 内存分析教程及例子

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 两个核心内存数据源(截图里会对应看到)

  1. android.mem(内存计数器):无需Root,看PSS/RSS/Dalvik堆/Native堆/匿名内存整体曲线,判断是否持续上涨(泄漏判定依据)
  2. heapprofd堆剖析器:Android 9+,需要Root,抓取malloc/new分配调用栈,生成火焰图,精准定位哪行代码未释放内存

1.3 环境准备(操作前置)

  1. 手机开启:开发者选项 → USB调试
  2. Root设备执行(模拟器直接生效)
bash 复制代码
adb root
adb shell setprop persist.traced.enable 1
  1. 分析网页:https://ui.perfetto.dev/

二、第一步:网页端可视化抓取(带界面截图标注步骤)

2.1 进入录制页面截图说明

  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 开始录制+复现问题

  1. 点击 ADD ADB DEVICE 选择已adb连接手机
  2. START RECORDING 开始录制
  3. 手机端重复复现操作:反复打开关闭页面、滑动列表、加载大图、播放视频
  4. 倒计时结束自动下载 .perfetto-trace 文件,拖拽进网页打开

三、第二步:Trace界面逐层解读(每一块对应截图视图说明)

3.1 顶层:内存时序轨道(判断是否泄漏,最直观)

打开trace后,左侧时间轴找到你的App进程,展开 Memory 轨道:

截图上5条核心曲线含义

  1. PSS(最重要)
    • 正常:操作后页面销毁+GC,内存下降,波浪起伏
    • 泄漏:每一次操作阶梯式上涨,永不回落,斜线持续走高 → 实锤内存泄漏
  2. Dalvik Heap:Java堆(对象、数组、字符串、静态变量)
  3. Native Heap:C++ malloc、Bitmap像素缓冲区、FFmpeg、OpenGL、第三方SO库内存(7.0后Bitmap存在这里,MAT看不到)
  4. Anonymous RSS 匿名内存:mmap内存映射、内存池、帧缓冲区,很多OOM元凶
  5. Stack / Code:线程栈、dex/so代码段,一般不会大幅波动

配套GC轨道联动查看

下方 Android ART GC 彩色竖线:频繁密集竖线 = 内存抖动,大量临时对象循环创建未回收。

3.2 核心:Heap Profile 堆快照火焰图(定位泄漏代码,截图重点)

1. 找到dump标记

时间轴上 黑色菱形小图标 = heapprofd手动/自动堆dump点,点击菱形弹窗打开火焰图。

2. 两种视图切换(UI右上角下拉框)

  1. Size 总分配大小:找占用内存最大的代码路径(内存大户)
  2. 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实用操作小技巧(截图按钮位置)

  1. 顶部搜索框输入包名,过滤只显示目标进程
  2. 右侧 Metrics → 查看内存增量统计表格
  3. 底部 Query(SQL) 执行SQL做批量量化分析

四、三个可直接复现的实战案例(带现象+Perfetto截图表现+修复)

案例1:Java静态变量持有Activity泄漏(最常见)

问题代码

java 复制代码
public class GlobalListener {
    // 静态变量强引用页面,onDestroy无法回收
    public static View.OnClickListener sClick;
}

Perfetto Trace截图表现

  1. Dalvik Heap曲线:反复开关页面持续上涨,退出页面不回落
  2. Heap火焰图Unreleased Size:顶层直接指向 DetailActivity 被静态类持有

修复

  1. onDestroy() 置空静态引用
  2. WeakReference弱引用包装回调
  3. LeakCanary二次确认引用链

案例2:大图Bitmap Native内存泄漏(Android高频坑)

现象

瀑布流无限滑动,Native Heap直线飙升,最终OOM崩溃,MAT查不到任何问题。

Perfetto截图特征

  1. Native堆曲线陡峭上涨,Dalvik堆变化很小
  2. 堆快照火焰图大量 BitmapFactory.nativeDecode 未释放栈

原因

Android 7.0+ Bitmap像素存在Native堆;列表Item不复用、大图未压缩、未调用recycle()

修复

  1. Glide/Coil设置采样率 inSampleSize
  2. 页面销毁遍历ImageView recycle Bitmap
  3. 开启图片内存复用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;

七、所有截图点位汇总(你打开网页后对应找即可)

  1. 录制页截图点:Record new trace → Data Sources勾选框 → Package过滤输入框
  2. Trace总览截图点:进程Memory轨道PSS/Native/Dalvik三条曲线
  3. GC截图点:ART GC密集竖线(内存抖动)
  4. Heap Dump截图点:时间轴黑色菱形标记
  5. 火焰图截图点:Unreleased Size视图、调用栈层级、Focus过滤按钮
  6. SQL截图点:底部Query输入框执行结果表格

八、避坑总结(踩过的坑)

  1. heapprofd堆快照必须Root,非Root只能看内存曲线,不能拿调用栈
  2. Release包代码混淆,火焰图是a.b.c类名,需要上传mapping.txt解析符号
  3. 采样间隔1024字节够用,改成1字节精度最高但trace文件巨大
  4. 不要把"内存占用高"等同于泄漏,页面销毁内存不回落才是泄漏
  5. 匿名内存暴涨优先排查mmap、OpenGL纹理、音视频缓冲区

如果你需要,我可以:

  1. 给你每一步对应的AI生成标注版高清示意图
  2. 或者给一份Windows批处理一键抓内存泄漏脚本。
相关推荐
超开心~1 小时前
Android AAudio介绍及流程分析
android
取个名字太难了~1 小时前
最新手机外接相机实时传输 + 动态美颜完整项目源码
android·数码相机·智能手机·美颜·相机连接·demu
say_fall1 小时前
【Linux系统编程】文件操作基础:C标准库、系统调用、fd是什么和fd与FILE*的关系
android·linux·c语言
ii_best11 小时前
更新!移动端开发软件按键安卓版&手机助手v5.1.0上线!本地AI识别全面解锁,脚本开发再升级
android·人工智能·ios·按键精灵
方乐寺村11 小时前
s,使用libpng提升png图片的保存速度。接下来本文将阐述在Android中如何集成libpng,以及在使用过程中遇到的问题和最 ...
android
name好难取诶12 小时前
PHP 静态分析工具实战 PHPStan 和 Psalm 完全指南
android·开发语言·php
Lydia ,12 小时前
安卓基础-线性布局
android
小小勇气大爆发15 小时前
Android四大组件总结
android
码农coding16 小时前
android16 SystemUI之StatusBar启动
android