内存泄漏系列专题分析之五:使用malloc_debug定位C/C++ native heap内存泄露
【关注我后续持续新增专题博文谢谢】上一篇我们讲了内存泄漏系列专题分析之四Android malloc_debug工具在Camera领域使用中预览卡死的瓶颈限制问题和二次改造这一篇我们开始讲内存泄漏系列专题分析之五使用malloc_debug定位C/C native heap内存泄露目录一、Malloc Debug配置说明1.1常见配置1.2malloc debug log日志1.3native_heapdump_viewer.py 使用二、使用Malloc Debug2.1打开Malloc Debug2.2分析原始dump文件2.3native_heapdump_viewer.py 格式化2.4解析调用栈2.5分析调用栈对应代码2.6总结本文主要介绍使用Google原生Android自带的malloc debug帮助定位C/C native heap内存泄露的方法。一、Malloc Debug配置说明1.1常见配置front_guard[SIZE_BYTES] 每次调用 malloc ,都在分配的区域之前填充 SIZE_BYTES ,填充内容为 0xaarear_guard[SIZE_BYTES] 每次调用 malloc ,都在指向内存的最后填充 SIZE_BYTES ,填充内容为 0xbbguard[SIZE_BYTES] 这个选项包含了 front_guard 和 rear_guard 。在指向内存的前后连 续 SIZE_BYTES 分别填充 0xaa 和 0xbbbacktrace[MAX_FRAMES] 这 个 optipn s 会 将 内存分配的速度减慢一个数量级 ,MAX_FRAMES 最大 值 256 默认值 16 。 每次在调用 malloc 时 , malloc debug 都会记录 malloc 的调用栈 (trace).栈的最大深度为 MAX_FRAMES , MAX_FRAMES 越大 , 对 malloc 的性能影响越大 , 也就越慢。当进程收到信号 SIGRTMAX - 17 Android 通常该信号值为 47 时, 会触发 malloc debug 的 dump heap trace 功能。 默认 dump 路径在 /data/local/tmp/ backtrace_heap.PID.txt 。给进程发信号通过 kill -s 47 PID , 进程收 到信号后并不会马上 dump backtrace , 而是会等到下次调用 malloc 或者 free 时 才会触发。所以如果发送信号后没有产生 trace 文件,请继续针对调试的进程做 测试。执行 kill -s 47 PID 之后,系统开始进行等待用户执行 malloc 操作。 举例 setprop libc.debug.malloc.options backtrace5 得到的 dump 文件为 $pid.txt 结尾backtrace_enable_on_signal[MAX_FRAMES] :使能这个选项 , 通过给进程发送信号 45 , 可以动态开启和关闭 backtrace 功能 。backtrace_dump_on_exit进程退出后自动 dump trace 文件,得到的 dump 文件为 $pid.exit.txt 结尾backtrace_dump_prefixtrace dump 的路径 , 如果需要放置其他目录 , 如 /sdcard/heap, 则 dump 的文件路 径为 /sdcard/heap.$PID.txtleak_track程序结束后,如果有未 free 的指针, logcat 中会打印出来12-19 14:54:32.583 7971 7971 E malloc_debug: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 12-19 14:55:02.585 7971 7971 E malloc_debug: androidtest leaked block of size 3072 at 0x736e78f030 (leak 1 of 7) // 内存泄露的 log ,直到程序退出未释放的内存 12-19 14:55:02.585 7971 7971 E malloc_debug: Backtrace at time of allocation: 12-19 14:55:02.585 7971 7971 E malloc_debug: #00 pc 00000000000152b0 /apex/com.android.runtime/lib64/libc_malloc_debug.so (debug_calloc432) 12-19 14:55:02.585 7971 7971 E malloc_debug: #01 pc 000000000000114c /system/bin/androidtest // 通过 addr2line -e symbols/system/bin/androidtest 000000000000114c 可以得到具体是哪一个指针未释放内存。 12-19 14:55:02.585 7971 7971 E malloc_debug: #02 pc 000000000007d86c /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init108) 12-19 14:55:02.585 7971 7971 E malloc_debug: #03 pc 000000000000104c /system/bin/androidtest 12-19 14:55:02.585 7971 7971 E malloc_debug: #04 pc 00000000000533f4 /apex/com.android.runtime/bin/linker64 12-19 14:55:02.585 7971 7971 E malloc_debug: androidtest leaked block of size 88 at 0x736e631030 (leak 2 of 7)record_allocs[TOTAL_ENTRIES]该选项很占内存 , 建议不开启。对进程中使用 malloc, calloc, realloc 的地方进行记录,打印的格式如下Threadid: action pointer size 471: malloc 0x72e00330c0 6 471: realloc 0x72e0005220 0x72e00330c0 12 471: free 0x72e012fcc0 471: free 0x72e0005220 471: malloc 0x72e01ade40 56 471: malloc 0x72e00330c0 6 注意,最大记录 8,000,000 条1.2malloc debug log日志verbose 打开 malloc debug 更多 log ,类似08-16 15:54:16.060 26947 26947 I libc : /system/bin/app_process64: malloc debug enabled09-10 01:03:50.070 557 557 I malloc_debug: /system/bin/audioserver: Run: kill -47 557 to dump the backtrace.1.3native_heapdump_viewer.py 使用该工具可以将 dump trace 文件通过符号表得到当前 未释放内存的指针在代 码中的行号 。准确性依赖 trace 保存栈的最大深度。所以最好有两份该文件,分 别是不同占用内存时 dump 得到的。对比查看哪个指针嫌疑最大,缩小范围继 续排查。其中 symbols 路径要正确二、使用Malloc Debug本文主要介绍使用Google原生Android自带的malloc debug帮助定位C/C native heap内存泄露的方法。2.1打开Malloc Debug针对android.hardware.graphics.composer2.1-service打开malloc debug#!/bin/bash# 需要重启上层让 property 生效stop# 指定 debug 参数。可以默认这样写更多选项参考# bionic/libc/malloc_debug/README.mdsetproplibc.debug.malloc.optionsbacktrace16 guard8 fill_on_free16# 指定要开malloc debug的进程setproplibc.debug.malloc.programandroid.hardware.graphics.composer2.1-service# 进程重启用libc_deubg.so替换原来的libc.SO库mallocDebug代码生效kill -9 $(pidof android.hardware.graphics.composer2.1-service)start# 需要关闭SELinux和目录读写权限setenforce 0chmod 777 /data/local/tmp# 触发Malloc Debug dump默认会生成如下dump文件# /data/local/tmp/backtrace\_heap.**PID**.txtkill -47 $(pidof android.hardware.graphics.composer2.1-service)2.2分析原始dump文件原始dump展现形式:1. 第一部分主要包含分配内存的大小分配的次数分配内存的函数调用栈地址。相同的调用栈用一行表示。z 0 sz 242952 num 1 bt 776cd9667c 776cd964a8 776d7977bc 776a16e384 776a16e6d0 776a16e4d8 776a1701bc 776a16fff0 776a188b48 776a188a0c 776a175394 776a179038 7769881260 7769878458 7769862208 776985ce4c z 0 sz 163840 num 1 bt 776cd9667c 776cd964a8 776a1937b4 776a1913d4 776a198cf8 776a199128 776a16fa80 776a1703cc 776a170328 776a188a30 776a175394 776a179038 7769881260 7769878458 7769862208 776985ce4c z 0 sz 163840 num 1 bt 776cd9667c 776cd964a8 776a19362c 776a1912e0 776a16f164 776a16e394 776a16e6d0 776a16e4d8 776a1701bc 776a16fff0 776a188b48 776a188a0c 776a175394 776a179038 7769881260 77698784582. 第二部分包含进程二进制代码在进程空间内加载的地址。配合第一部分可以确认分配函数的调用栈。7765e92000-7765e9d000 --xp 00009000 fd:08 3329 /vendor/lib64/hw/gralloc.mt6885.so 7765e9d000-7765e9e000 rw-p 00014000 fd:08 3329 /vendor/lib64/hw/gralloc.mt6885.so 7765e9e000-7765e9f000 r--p 00015000 fd:08 3329 /vendor/lib64/hw/gralloc.mt6885.so 7765e9f000-7765ea0000 rw-p 00000000 00:00 0 [anon:.bss] 7765ec4000-7765ed9000 r--p 00000000 fd:07 3965 /system/lib64/libEGL.so 7765ed9000-7765ef1000 --xp 00015000 fd:07 3965 /system/lib64/libEGL.so 7765ef1000-7765ef2000 rw-p 0002d000 fd:07 3965 /system/lib64/libEGL.so 7765ef2000-7765ef7000 r--p 0002e000 fd:07 3965 /system/lib64/libEGL.so2.3native_heapdump_viewer.py 格式化原始的dump文件可读性差可以使用 development/scripts/native_heapdump_viewer.py 格式化转换为树状结构转换后的dump文件会有很多分配内存的堆栈信息。这里只提取了内存泄漏最严重的一个堆栈为了方便查看只截取了每行的前面部分:BYTES %TOTAL %PARENT COUNT ADDR LIBRARY FUNCTION LOCATION 0 0.00% 0.00% 0 APP 181538320 100.00% 100.00% 566960 ZYGOTE 152077336 83.77% 83.77% 391966 776d8aa2cc /system/lib64/vndk-29/android.hardware.graphics.co 152077336 83.77% 100.00% 391966 776d8a9628 /system/lib64/vndk-29/android.hardware.graphics. 152077336 83.77% 100.00% 391966 776ba1a598 /vendor/lib64/hw/android.hardware.graphics.com 152077336 83.77% 100.00% 391966 776ba1d0b4 /vendor/lib64/hw/android.hardware.graphics.c 147506972 81.25% 96.99% 380182 776ba1e4f0 /vendor/lib64/hw/android.hardware.graphics 147506972 81.25% 100.00% 380182 776ba1f654 /vendor/lib64/hw/android.hardware.graphi 147506972 81.25% 100.00% 380182 776ba17704 /vendor/lib64/hw/android.hardware.grap 147506972 81.25% 100.00% 380182 7769850744 /vendor/lib64/hw/hwcomposer.mt6885.s 147506048 81.25% 100.00% 380171 776988dfd8 /vendor/lib64/hw/hwcomposer.mt6885 147505960 81.25% 100.00% 380170 7769859c3c /vendor/lib64/hw/hwcomposer.mt68 147505960 81.25% 100.00% 380170 7769862134 /vendor/lib64/hw/hwcomposer.mt 147505960 81.25% 100.00% 380170 7769875bd0 /vendor/lib64/hw/hwcomposer. 147505960 81.25% 100.00% 380170 776a176bd8 /vendor/lib64/libdpframworks 147505960 81.25% 100.00% 380170 776d7977bc /system/lib64/vndk-sp-29 147505960 81.25% 100.00% 380170 776cd964a8 /system/lib64/libc_mal文件记录了当前进程所有使用malloc系列接口分配内存的操作每次还没有释放的分配计数为1是潜在的内存泄露。排在最前面的是持有内存最多的操作。可以看到composer进程中使用内存最多的是libdpframworks模块共使用内存147505960Kb还有380170次分配的内存没有释放在composer所有还没有释放的分配中占比81.25%。显然这个调用栈非常可疑。我们重点关注这一行147505960 81.25% 100.00% 380170 776a176bd8 /vendor/lib64/libdpframworks2.4解析调用栈查看调用栈我们容易知道分配内存的地方在如下的代码中147505960 81.25% 100.00% 380170 776a176bd8 /vendor/lib64/libdpframework.soDpAsyncBlitStream2::createJob(unsigned int, int) vendor/mediatek/proprietary/hardware/dpframework/common/stream/DpAsyncBlitStream2.cpp:85DP_STATUS_ENUM DpAsyncBlitStream2::createJob(uint32_t jobID, int32_t fence) { DP_STATUS_ENUM status; struct AsyncBlitJobPair *pJob; int32_t fenceFD, fenceFD2; //分配内存 pJob new struct AsyncBlitJobPair; if (pJob NULL) { DPLOGE(DpAsyncBlitStream2: cannot allocate job\n); return DP_STATUS_OUT_OF_MEMORY; } }2.5分析调用栈对应代码跟踪pJob的生命周期我们最终定位到内存泄露的位置DP_STATUS_ENUM DpAsyncBlitStream2::invalidate(struct timeval *endTime) { DP_STATUS_ENUM status; struct AsyncBlitJobPair *pJob; { ... pJob m_jobList.front(); } ... { AutoMutex lock(m_pJobMutex); m_jobList.erase(m_jobList.begin()); //从链表取出使用后忘记释放资源. 这里应该有delete delete pJob; }2.6总结使用Malloc Debug工具我们关心BYTES和%TOTAL两个指标。如果这两个指标一直偏大就标明可能存在内存泄露。【关注我后续持续新增专题博文谢谢】下一篇讲解内存泄漏系列专题分析之六高通camx 内存泄漏测试的未回收问题分析

相关新闻

MCP 接入后,桌面 Agent 如何秒变「万能插座」?3 个办公自动化案例实测

MCP 接入后,桌面 Agent 如何秒变「万能插座」?3 个办公自动化案例实测

从封闭系统到开放工作台 上周用 Python 脚本批量处理 Excel 时,发现要反复切换浏览器查数据、开 IDE 跑清洗、再用邮件客户端发结果——这种碎片化操作在办公场景太常见。直到把有道 Lobster 的 MCP(Multi-Channel Processor)模块接进来&…

2026/9/22 10:16:42 阅读更多 →
Reducer 是什么?多个节点如何安全更新状态

Reducer 是什么?多个节点如何安全更新状态

上一篇文章里,我们讨论了 Edge 和 Conditional Edge。 一个重要结论是: Edge 让流程可以分支,也可以回到前面的节点。 但一旦流程变复杂,就会遇到另一个问题: 多个节点如何安全更新同一份 State? 这就是 Re…

2026/9/18 7:59:31 阅读更多 →
计算机毕业设计之基于Spring Boot的洗衣店智能服务系统的设计与实现

计算机毕业设计之基于Spring Boot的洗衣店智能服务系统的设计与实现

随着人们生活水平的提高和生活节奏的加快,人们对衣物清洁的需求不断增加,传统的洗衣服务模式已难以满足现代消费者对便捷、高效、个性化服务的追求。因此,开发一套基于Spring Boot的洗衣店智能服务系统成为当务之急。该系统旨在通过信息化手段…

2026/9/21 21:09:30 阅读更多 →

最新新闻

3分钟解决眼图配置卡壳问题,一文搞懂核心原理

3分钟解决眼图配置卡壳问题,一文搞懂核心原理

3分钟解决眼图配置卡壳问题,一文搞懂核心原理 刚接手信号完整性项目,想跑个眼图仿真,结果光是在环境配置上就折腾了整整一下午。Python包版本冲突,依赖库缺失,最后连个简单的正弦波都画不出来。这种“配置环境就卡半天”的挫败感,做过嵌入式或通…

2026/9/22 16:26:22 阅读更多 →
3分钟一文搞懂多闪和抖音的区别

3分钟一文搞懂多闪和抖音的区别

3分钟一文搞懂多闪和抖音的区别 官方文档太长抓不住重点?别急,这篇帮你 一文搞懂 多闪和抖音的区别。很多刚入行的同学,甚至做了几年开发的老鸟,在面试时被问到这两个产品的底层逻辑差异,往往卡壳。为什么?因为大家习惯了看代码,却忽略了产品形态对…

2026/9/22 16:26:22 阅读更多 →
私募基金从业资格考试手写实现

私募基金从业资格考试手写实现

3天吃透私募基金从业资格:从手写代码到通关的入门到精通指南 刚拿到Python教程,连Hello World都能跑,但让你搭个基金数据清洗项目,脑子瞬间空白。这就是多数人的死穴:语法会背,实战掉链子。别慌,今天这篇《私募基金从业资格考试》备…

2026/9/22 16:26:22 阅读更多 →
dnf怎么去天界源码解析

dnf怎么去天界源码解析

DNF去天界实战:3步搞定源码级原理,从入门到精通 面试被问原理答不上来?别慌,这不仅是DNF玩家的痛点,更是开发者的通病。很多应届生在技术面试中,面对“如何实现角色跨区域传送”或“服务端状态同步”这类问题,只能支支吾吾,根本说不出个所以然…

2026/9/22 16:26:22 阅读更多 →
5个开路性能坑点 新手避坑指南 提升3倍速

5个开路性能坑点 新手避坑指南 提升3倍速

5个开路性能坑点 新手避坑指南 提升3倍速 配置环境就卡半天,编译报错刷屏到怀疑人生,这种痛苦每个刚接触高性能开发的新手都懂。别急着换电脑,大概率是代码里的“开路”逻辑没理顺,导致I/O阻塞或内存溢出。很多新手避坑指南只讲理论,却忽略了实际…

2026/9/22 16:26:22 阅读更多 →
手写实现狗狗照片压缩算法面试不再慌

手写实现狗狗照片压缩算法面试不再慌

手写实现狗狗照片压缩算法面试不再慌 面试被问原理答不上来,是不是特别尴尬?别慌,很多转岗的兄弟都卡在这。今天咱们不聊虚的,直接拆解 狗狗照片 处理中的核心逻辑,用 手写实现 的方式把底层搞透。…

2026/9/22 16:25:22 阅读更多 →

日新闻

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天 配置环境就卡半天?别怪机器慢,多半是你没选对工具链。在Java、Go或Python的项目现场, 手写实现…

2026/9/22 0:00:41 阅读更多 →
剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑 面试被问原理答不上来,是不是常态?别慌。很多开发者对着 GitHub 开源仓库里的代码发呆,看似简单实则暗藏玄机。今天这份【剑帝加点】速查手册,直接带你拆解核心实现,把面试必考的原理讲透。…

2026/9/22 0:00:41 阅读更多 →
手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优 复制来的代码跑不通不知道怎么调?别慌,这种“复制粘贴地狱”在开发圈太常见了。尤其是做 图片压缩网站…

2026/9/22 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/22 4:32:41 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/22 4:38:57 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/22 8:51:04 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/21 15:36:51 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/21 15:36:51 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/22 2:43:42 阅读更多 →