26. 内存管理与泄漏排查:媒体内存分配策略、Native Heap分析、常见泄漏模式
内存泄漏,说白了就是“借了不还”。在Stagefright这种长期运行的媒体服务里,哪怕一次泄漏几KB,跑上几个小时,系统就可能卡顿甚至崩溃。我见过不少App因为MediaCodec没释放,最后被系统直接杀掉——嗯,这种问题排查起来特别头疼。
今天我们就来聊聊Stagefright的内存管理。我会结合自己的踩坑经历,帮你理清分配策略、分析工具,以及那些最容易出问题的泄漏模式。
26.1 媒体内存分配策略
Stagefright的内存分配,不是简单的malloc/free。它有一套自己的策略,主要分三层:
| 层级 | 分配方式 | 典型场景 |
|---|---|---|
| 应用层 | Java Heap / ByteBuffer | MediaCodec输入输出缓冲区 |
| Native层 | IMemory / MemoryHeapBase | 解码后的原始帧数据 |
| 驱动层 | ION / DMA-BUF | 硬件编解码器直接访问 |
我个人习惯把重点放在Native层。因为大部分泄漏都发生在这里——Java层有GC兜底,驱动层有内核回收,唯独Native层,全靠开发者自觉。
26.2 Native Heap分析
Native Heap泄漏,是Stagefright里最常见的“隐形杀手”。为什么说它隐形?因为系统不会主动告诉你,只有等到OOM了,你才后知后觉。
26.2.1 常用分析工具
- malloc debug:Android自带的Native内存调试工具。设置
libc.debug.malloc=1,就能追踪每次分配和释放。 - AddressSanitizer (ASan):编译时开启,能检测越界和use-after-free。不过性能开销大,适合调试版。
- SimplePerf / Perfetto:用来分析内存分配热点,看哪个函数分配最多。
26.2.2 实战:用malloc debug定位泄漏
假设你怀疑MediaCodec解码后内存没释放。可以这样操作:
# 开启malloc debug
adb shell setprop libc.debug.malloc 1
adb shell stop
adb shell start
# 复现问题后,抓取分配日志
adb shell dumpsys meminfo mediaserver
# 或者用更细粒度的方式
adb shell am start -n com.example/.MainActivity
adb shell kill -3 $(pidof mediaserver)
拿到日志后,重点关注Native Heap那一栏。如果Pss持续增长,说明有泄漏。我曾经在某个项目里,发现每次播放视频后,Native Heap都会涨2MB,最后定位到是MediaCodec的dequeueOutputBuffer返回的buffer没及时release。
26.3 常见泄漏模式
根据我的经验,Stagefright里的泄漏,翻来覆去就那么几种模式。我总结了一下:
26.3.1 Buffer未释放
这是最常见的。解码器输出buffer,你用完了,但没调用releaseOutputBuffer。或者调用了,但参数传错了——比如传了false而不是true,导致buffer没真正还给解码器。
26.3.2 循环引用
Stagefright里大量使用sp<>智能指针。如果两个对象互相持有对方的sp,就会形成循环引用,导致两者都无法释放。
举个例子:
class MediaCodecSource : public MediaSource {
sp<MediaCodec> mCodec;
};
class MediaCodec : public RefBase {
sp<MediaCodecSource> mSource; // 循环引用!
};
解决办法?用wp<>弱指针打破循环。或者手动在析构函数里置空。
26.3.3 匿名共享内存泄漏
Stagefright经常用MemoryHeapBase分配匿名共享内存。如果MemoryHeapBase对象销毁时,对应的fd没有关闭,就会泄漏。这种泄漏在/proc/self/maps里能看到大量/dev/ashmem映射。
我记得有一次,客户反馈播放4K视频时,系统内存暴涨。查了半天,发现是OMXCodec里有个MemoryHeapBase在异常路径下没释放。修复后,内存占用直接降了30%。
26.4 知识体系图
下面这张图,帮你理清Stagefright内存管理的核心脉络:
26.5 避坑指南
最后,分享几个我踩过的坑:
- 不要依赖智能指针的自动释放。在异步回调里,
sp的引用计数可能因为多线程问题而异常。我建议在关键路径上手动调用clear()。 - 注意HAL层的实现差异。不同厂商的OMX实现,对buffer生命周期的管理可能不同。比如有的厂商要求你显式调用
freeBuffer,有的则自动回收。最好在集成测试里覆盖一下。 - 善用
libmemtrack。它能帮你追踪GPU/多媒体相关的内存分配。我在排查一个视频卡顿问题时,就是靠它发现某个厂商的HAL层泄漏了连续物理内存。
一句话总结: Stagefright的内存管理,核心就是“谁分配,谁释放”。但难点在于异步回调、多线程、以及厂商实现的差异。用好malloc debug和dumpsys,大部分泄漏都能快速定位。
公众号:蓝海资料掘金营,微信deep3321