ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

一条命令三步定位内存泄漏:Perfetto heapprofd 实战完整指南

一条命令三步定位内存泄漏:Perfetto heapprofd 实战完整指南 一条命令三步定位内存泄漏Perfetto heapprofd 实战完整指南【免费下载链接】perfettoProduction-grade client-side tracing, profiling, and analysis for complex software systems.项目地址: https://gitcode.com/GitHub_Trending/pe/perfetto你有没有遇到过这种情况应用在用户手里用半小时越来越卡最后被系统杀掉而你自己的测试机上完全复现不出来内存类崩溃是 Android 线上最顽固的问题之一——系统内存吃紧时lmkd 会按 oom_score_adj 从高到低杀掉缓存中的应用而一个每次操作只泄漏 2MB 的缺陷30 次操作后就能吃掉 60MB足以让你的 App 成为第一批被杀的对象。Perfetto 的 heapprofd采样式 native 内存剖析器可以在真机上拦截 malloc/free 调用把每一笔未释放的内存归因到具体调用栈采样间隔默认 4096 字节对前台应用几乎无感。本文用 3 条命令带你走完录制—读图—修复验证的全流程。一个复现不出来的泄漏商品列表越滑越吃内存先讲一个典型的排查现场。某资讯类 App 收到用户反馈连续下滑信息流 20 分钟后应用被杀重启后继续。开发环境用 LeakCanary 跑了 30 分钟Java 堆没有增长查无实据。问题在于LeakCanary 只看 Java 对象引用而内存增长的元凶是 native 侧——dumpsys meminfo显示每次滚动 10 次Native Heap 的 Private Dirty 稳定上涨约 2MBJava 堆纹丝不动。排查分三步走确认增长曲线用 Perfetto 录制 30 秒内存计数器滚动列表观察mem.rss.anon进程匿名内存即真正的物理占用是否单调上升归因到调用栈用 heapprofd 录制同一段操作看火焰图里哪条调用路径的未释放内存在涨定位代码火焰图里锁定到离屏渲染缩略图的 EGL 路径——每个缩略图新建了渲染上下文却从未销毁1.6MB 的纹理缓冲跟着上下文一起被挂住。这个案例的关键启示Java 堆干净不代表没有泄漏native 分配哪怕你一行 C 都没写JNI 背后的框架 API 也会走 malloc必须单独查。录制第一条 native 内存 profile最简 heap_profile 命令前置条件设备 Android 10已连接 ADB在 user 版本系统上目标 App 的 manifest 需要带profileable android:shelltrue/或 debuggable 标记否则 profile 会是空的。仓库里没有现成脚本的话先 clone 一份git clone https://gitcode.com/GitHub_Trending/pe/perfetto cd perfetto下面这条命令录制 30 秒-d单位是毫秒的 native 堆 profile每 5 秒出一个快照-c 5000结束后在/tmp/heap_profile-latest下生成 raw-tracetools/heap_profile android -n com.example.news -d 30000 -c 5000录制期间反复执行你的复现路径滚动、切换页面然后 Ctrl-C 结束。输出里会出现Wrote profiles to /tmp/... (symlink /tmp/heap_profile-latest)把它指向的raw-trace文件拖进 Perfetto UI 的网页前端即可查看。图1Perfetto UI 录制页的 Native heap profiling 配置项目标进程名、采样间隔默认 4096B、连续快照间隔⚠️新手最大的误区以为 heapprofd 能看到之前的分配。它是非回溯式的——只统计开始录制之后发生的 malloc/free启动时就已经存在的内存它一概不知。所以如果你的问题是进程现在为什么这么大heapprofd 回答不了只有当泄漏会随时间持续发生时绝大多数泄漏都是才能靠观察增量抓住它。另一个隐蔽的坑多个录制会话指向同一个进程时只有第一个生效如果 profile 为空先adb shell killall perfetto清掉残留会话再重试。读懂火焰图未释放内存的 4 个度量维度打开 raw-trace点时间轴上菱形标记的 Native heap profile 切片默认展示 Unreleased Malloc Size 火焰图横轴是宽度大小顶层是main()/pthread_start等入口越往下越接近最终的malloc调用点。根节点标注的总 MiB 就是本次记录期内分配了但没 free 掉的总量。图2Native heap profile 火焰图默认按 Unreleased Malloc Size 聚合未释放内存根节点显示 46.31 MiB 总量四个度量模式各查各的病火焰图左上角可切换Unreleased Malloc Size默认未释放字节数查泄漏主战场Unreleased Malloc Count未释放分配次数专抓单个很小、数量巨大的碎片泄漏Total Malloc Size含已释放的总分配量查分配热点churn比如频繁创建销毁的临时对象Total Malloc Count总分配次数看 allocator 压力。开了-c连续快照后时间轴上会排开多个切片每个切片对应 5 秒窗口选中相邻的多个切片还能做窗口合并对比泄漏路径会随滑动次数明显变宽图3开启 -c 5000 连续快照后时间轴上按时间窗排列的多个 Native heap profile 切片图形看不爽就上 SQL。Perfetto 把堆数据存进标准表UI 的 Query (SQL) 标签页或命令行trace_processor query都能跑tools/trace_processor query /tmp/heap_profile-latest/raw-trace \ INCLUDE PERFETTO MODULE android.memory.heap_profile.summary_tree; SELECT name, self_size, cumulative_size FROM android_heap_profile_summary_tree WHERE cumulative_size 1048576 ORDER BY cumulative_size DESC LIMIT 10;self_size是该帧作为叶子时的未释放字节数cumulative_size是出现在任意层级的未释放字节数——排序后 Top 10 基本就是修复优先级表。图4UI 的 Query (SQL) 窗口上半区写 PerfettoSQL下半区看结果表案例闭环两轮录制拿到修复前后的对比数据回到信息流案例。修复前的量化基线30 秒滚动5 个连续快照Native Heap PSS48MB → 110MB净增 62MB火焰图eglCreateContext路径 cumulative_size 约 60.9MB且每个快照切片比上一个宽 2MB 左右mem.rss.anon计数器单调上行无回落。修复动作把 EGL 上下文收敛为进程内单例复用销毁缩略图时同步释放纹理。修复后用完全相同的命令和复现路径再录一次这是闭环的关键——变量只有一个就是代码tools/heap_profile android -n com.example.news -d 30000 -c 5000对比结果同样 30 秒滚动Native Heap PSS 48MB → 51.8MB净增 3.8MB其中 2.9MB 是缓存正常水位eglCreateContext路径 cumulative_size 从 60.9MB 降到 4.2MB降幅 93%mem.rss.anon曲线在滑动停止后回落到基线附近。图5mem.rss.anon 等内存计数器时间线RSS 尖峰与用户操作时间点一一对应是验证泄漏是否复发的直观依据工具选型Java 堆、native 堆、内核内存各查哪份数据Perfetto 的内存数据源不止 heapprofd选错工具会白忙一场。按你想回答什么问题对号入座你要回答的问题数据源最低版本触发方式native 内存去哪了 / 谁在泄漏malloc 级heapprofd native profileAndroid 10tools/heap_profile androidJava/Kotlin 对象被谁引用着ART heap dumpAndroid 11tools/java_heap_dump -n 进程名Java 对象分配太频繁churnART allocation profilingAndroid 12heap_profile加--heaps com.android.art进程 RSS/swap 随时间的走势内存 ftrace 计数器内核 tracepointftracekmem/rss_stat等事件为什么被 lmkd 杀了oom_score_adj/lowmemory_kill内核 tracepointftrace 事件 linux.process_stats绕过 malloc 的直接 mmap 大块syscall 事件 / perf 全采样Android 14 / 12syscall_events: sys_mmap或 perfperiod: 1两点补充Java 堆 dump 是整张对象引用图的一次性快照支持回溯但看不到分配调用点和 heapprofd 正好互补细节见 Java 堆剖析文档 与 内存案例手册。文件映射内存dex、so用adb shell showmap PID更直接Perfetto 管不了这部分。图6ART heap dump 在 UI 中的火焰图视图按最短可达路径聚合 Java 对象占用可切换支配树视角进阶自定义分配器、采样权衡与后台场景自定义分配器对 heapprofd 不可见。如果你的 App 有自己的内存池图片缓存、解码器 buffer 常见用 heapprofd Custom Allocator API 把池子注册进来分配/释放时各报一笔就能进火焰图#include heap_profile.h static uint32_t g_heap AHeapProfile_registerHeap( AHeapInfo_create(example.image_cache)); void* my_malloc(size_t size) { void* ptr real_malloc(size); AHeapProfile_reportAllocation(g_heap, (uintptr_t)ptr, size); return ptr; // 释放侧对应调用 AHeapProfile_reportFree }采样间隔是精度和开销的旋钮-i默认 4096即平均每分配 4KiB 记一笔前台交互型 App 保持默认即可后台长时录制可以调到 8192 减半开销。大于间隔的大块分配不走采样按真实大小记录所以大泄漏不会因为采样被稀释。后台与低端设备场景录制时 App 退后台mem.rss.anon会告诉你它是否被 ZRAM 换出被 lmkd 杀掉的进程在lowmemory_kill事件里有时间戳可以和泄漏曲线对出死亡时刻。这些配置都基于 ftrace见 memory 案例手册。上线前内存健康自检清单把下面这些项塞进你的发布流程泄漏就不用等到线上录制前user 构建的 App 已打profileable android:shelltrue标记复现路径写成可重复操作的脚本化步骤滚动 30 次 / 切换 10 个页面已adb shell killall perfetto排除残留会话录制中连续快照间隔 ≤ 操作周期至少 5 个切片用于看趋势前台敏感场景未调低采样间隔导致数据稀疏读图时Unreleased 和 Total 两个维度都看过区分泄漏 vs churn火焰图里对libart.so加了 Hide Frame 过滤减少噪音SQL 里用cumulative_size排过 Top 10和图上看到的最大块对得上修复后用同一命令、同一复现路径重录对比 Native Heap 增量要求降幅 ≥90% 或给出解释低内存设备4GB RAM上跑通完整复现路径mem.rss.anon无单调爬升最后几条实践建议先定基线再谈优化没有修复前 30 秒的量化录制任何优化后内存降了都不可信。native 和 Java 两条线并行查dumpsys meminfo的 Private Dirty 分属两类堆LeakCanary 只管得住后者。把 heap_profile 接进 CItrace_processor query是无界面输出的泄漏回归可以做成一条卡发布门槛的自动化。火焰图配合代码读最大的块往往在公共路径上真正要修的是它下面第二、第三深的叶子帧。专业提示把-c 5000的连续快照作为日常巡检标配每次发版前跑一次并存档 raw-trace用cumulative_sizeTop 10 做版本间 diff能在用户报障前两周就抓到缓慢增长的新泄漏修复成本比线上救火低一个数量级。更多 SQL 写法见 PerfettoSQL 入门命令行细节见 trace_processor 参考 与 heap_profile 参考。【免费下载链接】perfettoProduction-grade client-side tracing, profiling, and analysis for complex software systems.项目地址: https://gitcode.com/GitHub_Trending/pe/perfetto创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表