你信坚持是金
也信无常命运
信天赋努力
信笨拙委屈
------ 《回声》那英
在本专栏前几篇文章里,笔者介绍过,使用 Perfetto 进行 Java Heap Dump 的方法。与通过 Android Studio Profiler 进行抓取类似,所得到的heap文件描述了应用在瞬时状态下的 堆内存分布 、对象引用关系 信息,对于分析内存泄漏、OOM 等问题很有帮助。与此同时,Perfetto 还提供了另一强有力的分析工具 ------ Heapprofd,它的优势在于能够 追踪 一段时间内,Native/Java 内存的全部 分配/回收 事件,从而发现是 哪些函数调用引起的内存上升。
环境要求
使用 heapprofd 工具时,对于手机、APP的配置有要求
- Android 版本:在10及以上
- 系统构建方式 :
- 如果是
debug/eng构建:可调试所有的应用和大部分系统服务 - 如果是
user构建- 待调试应用是debug包 ------ 可以抓取
- 待调试应用是release包 ------ 需在Manifest中声明
profileable
- 如果是
火焰图解读

火焰图并不复杂,甚至可以说相当直观。这是一张采样 Native 内存 的火焰图,每一块 矩形 对应一个 函数 ,其 面积大小 则意味着 该函数引起的内存分配大小。从上向下是函数的调用关系,纵向相邻的两个函数之间,上方的函数调用下方的函数。
可以选择查看不同的 Tab 的数据:
- Unreleased malloc size
- Total malloc size
- Unreleased malloc count
- Total malloc count
size 反映了内存分配总大小,count 体现出的是申请内存的次数。
进行采样的3种方式
Google 官方提供了3种进行内存分配采样的方式,它们在功能完整性、环境依赖上有所区别。
| 序号 | 方法简述 | 环境依赖 | 功能全面性 |
|---|---|---|---|
| 方式一 | 使用 adb shell perfetto 命令读取配置并执行 |
无 | 高,配置可编辑 |
| 方式二 | python 执行 tools/heap_profile 脚本 | python3 环境 | 高,脚本可编辑 |
| 方式三 | 使用 Perfetto网页工具 进行抓取 | 无 | 低,只能从配置预设里面选择,不支持 Java Heap Sampling |
方式一:使用 adb shell perfetto 命令读取配置并执行
config.pbtx
textproto
buffers {
size_kb: 65536
fill_policy: DISCARD
}
data_sources {
config {
name: "android.heapprofd"
heapprofd_config {
sampling_interval_bytes: 4096
process_cmdline: "com.google.samples.apps.nowinandroid"
shmem_size_bytes: 8388608
block_client: true
all_heaps: false
heaps: "com.android.art"
}
}
}
duration_ms: 10000
对于上述配置,关键参数解释如下:
| 参数 | 说明 |
|---|---|
| sampling_interval_bytes | 以多少字节数的粒度进行采样 |
| process_cmdline | 要抓取的包名,(debug) or (release + profileable) |
| heaps | com.android.art 表示采集虚拟机堆分配信息 |
对于 release 包,在 AndroidManifest.xml 中声明 profileable
xml
<manifest ...>
<application>
<profileable android:shell="true"/>
...
</application>
</manifest>
当然也可以增加想要抓取的其它信息作为配置,随后通过 adb shell perfetto 命令启动抓取,抓取完成后,将 trace.pftrace 文件pull到本地进行分析。
抓取命令
bash
cat config.pbtx | adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/trace.pftrace
方式二:python执行tools/heap_profile脚本
该脚本位于 github.com/google/perf... ,用参数 processName(-n com.google.samples.apps.nowinandroid)或者 PID(-p 1234)指明要抓取的进程。推荐使用 进程名 的方式,可以在进程启动前,就开始抓取任务(当然,进程尚未启动时,我们是不知道其 PID 的)。抓取到的数据保存为 raw-trace,可以用 Perfetto UI 查看。
方式三:使用 Perfetto UI 进行抓取
这种方式无法抓取 Java 内存分配信息,不推荐。

采样 Native 内存
对"采样"的理解
既然是"采样",说明并不是完整记录了全部的内存分配事件,而是 有选择地 。在 Perfetto 中,采样的粒度取决于 sampling_interval_bytes 参数,翻译过来就是 采样区间字节数,其默认值是 4KB,表示 每当内存累计分配超过4KB时,就记录这一次内存申请的函数调用。
通过使用这种"按字节流抽样"的方法,结合 Poisson sampling(泊松抽样),可以让统计更加稳健,既能覆盖高频小分配,也不会漏掉低频大分配。
在目标进程里,heapprofd 拦截了 libc 的 malloc/free 家族调用。
采样 Java 内存
Java Heap Sampling 是一种 抽样式 的 Java 对象分配记录机制,用于观察内存分配趋势。它记录的是
- Java 对象分配事件(allocation)
- 对象类型(class)
- 分配大小
- 时间
- 线程
一个典型的 Java 内存采样火焰图如下所示:

如何启用它
- 对于"方式一 ",在配置文件中增加
heaps: "com.android.art(上文的配置文件已经增加) - 对于"方式二 ",执行 python 脚本时,增加
--heaps com.android.art参数 - 对于"方式三",不支持
它会做什么
它抽样记录了一段时间内对象分配的数量和大小,与 Native Heap 类似,但发生在 ART 层。
它不会做什么
它不会扫描整个 Java Heap,也不会构建完整的 对象引用关系表 。------这是 heapdump、AS Profiler 的职责。
它不会 记录对象的回收和销毁 ,这是 ART 虚拟机 自动执行的,Perfetto 无法介入。
不知道什么原因,在同时开启 Java Heap Sampling 和 Java Heap Dumps 时,我只能在结果里得到前者,无法看到后者。
使用Perfetto分析内存问题的框架
