首页 > 技术 > 正文

Android Perfetto 系列 14:heapprofd 与内存 Profiling

2026-10-30  ·  阅读:162

实验室 leak 复现可以设 block_client: true 并配 block_client_timeout_us,用目标进程 malloc 路径的阻塞换完整性;交互性能分析不要默认开,因为它可能反向作用 jank/latency。交互场景通常用 block_client: false,再通过更高采样间隔、更大 shmem_size_bytes 和 stats 检查接受不完整风险:必须检查 heapprofd_buffer_overran、heapprofd_non_finalized_profile、heapprofd_last_profile_timestamp 是否覆盖方向窗口。

把 heapprofd 放进系统 Trace 时,意见同时保留少量业务 marker。App 内用 android.os.Trace / AndroidX Trace 会落到 atrace;Perfetto SDK Track Event 需要单独启用 track_event data source。

比如播放器场景至少要有 operation_start、steady_state、operation_end、release_done/gc_idle_done 四类 marker。没有 launch marker 时,结论只能写“操作后保留增加”,不能写“泄漏”。官方文档将 android.log 支持范围写为 userdebug;已 root 且 logd/SELinux 权限允许的 user 构建也可能采到,但不能仅凭 adb root 推广到所有设备。普通生产 user build 上不要把 logcat 当成这份配置默认能抓到的证据。

UI 怎么看 Heap Profile

导入 trace 后,在目标进程下找 Heap Profile 轨道。轨道上的 diamond 代表一次 dump。点击 diamond 后,底部会展示 flamegraph。

continuous dump 的每个 diamond 都是从记录开始到该时间点的累计结果。先看未释放量判断占住不放,再看总分配量判断 churn。读图时按这个顺序走:

  1. 看连续几个 diamond 的 Unreleased malloc size 是否持续增加。
  2. 看 Total malloc size,判断是不是分配 churn 很高但释放也快。
  3. 用 Left Heavy 视图找最大的调用栈。
  4. 先找 allocator / ART glue 之上的第一个 App、SDK 或业务帧。
  5. 回到时刻轴,关联触发操作、业务 Track Event、线程运行。

用连续 dump 观察保留变化

示意图(假设统计结果):连续 dump 的未释放量为 2→5→4 MiB;火焰图用于查看调用栈贡献,不能凭单点判泄漏。

不要用单个 diamond 判泄漏。泄漏判断至少要看“操作前、操作后、回收后”几个点。如果操作后上涨,回收后仍然不降,再看未释放调用栈。

实验室里可以用 adb shell killall -USR1 heapprofd 额外触发一次 snapshot。比如“进入页面前、页面稳定后、退出页面并完成 release/close/析构后”各触发一次,比只靠固定 5 秒间隔更容易把业务动作和 diamond 对上。

只有当 native backing 由 Java wrapper、NativeAllocationRegistry 或 Cleaner/finalizer 持有时,才需要把“等待 idle、finalizer、GC 完成”算进这个状态点。allocator cache 和碎片还可能导致 heapprofd 数字下降了、RSS 却不立刻下降。缓存不是“不释放”的委婉说法,必须有容量上限、命中/复用证据、退出或压力下回收策略;否则只能写“疑似长期保留”。

SQL 怎么查未释放分配

heapprofd 会写入调用栈相关表:

表含义
heap_profile_allocationallocation/free 的样本和大小
stack_profile_callsite调用栈节点
stack_profile_frame函数帧
stack_profile_mappingso、apk、jar、binary 映射
stack_profile_symbol离线符号化结果

单目标 profile 快速读图时,优先用官方标准库里的 summary tree。它会沿着 stack_profile_callsite.parent_id 聚合整条调用栈,结果更接近 flamegraph 里的 cumulative view:

1
2
3
4
5
6
7
8
9
10
INCLUDE PERFETTO MODULE android.memory.heap_profile.summary_tree;

SELECT
name,
mapping_name AS map_name,
cumulative_size / 1024.0 / 1024.0 AS unreleased_mb
FROM android_heap_profile_summary_tree
WHERE cumulative_size > 0
ORDER BY cumulative_size DESC
LIMIT 50;

summary tree 汇总整份 Trace 中所有进程、heap 和时间的记录;本例只适合仅采单进程 native heap 时查看末尾累计状态,不是任意 diamond 的快照。它没有进程列,不要直接用于多进程回归统计。多进程 App、:remote 进程或同名进程重启时,要回到原始表按 upid、PID、进程名和 diamond 时间点过滤,避免把别的进程归到目的进程。

如果要按进程、diamond 时刻点和 leaf frame 做粗查,再回到原始表。这条 SQL 计算截至最后一个 dump 结束时刻点的保留量,重点看 SUM(a.size):allocation 是正数,free 是负数,求和后更接近该 diamond 上的 Unreleased malloc size。它不用于最终归因,最终报告必须回到完整调用栈。这里的 heap_profile_allocation 存储每次 dump 相对上次的增量,不是每个 dump 的完整快照;必须累加 ts <= dump_ts。以下只统计 libc.malloc,避免把不会记录 free 的 com.android.art 混成未释放 native 内存。

本文由News整理发布,仅供参考。

12345678
评论加载中…

友情链接申请友链

联盟矩阵53