内存泄漏专题分析

问题背景

用Launcher跑monkey时存在泄露,2个小时USS从65M涨到205

分析过程

打开debug 15复测,adb shell dumpsys meminfo查看launcher memory用量如果大于300M时,
(1)adb shell dumpsys meminfo 抓取一份launcher memory使用信息
(2)adb shell kill -11 生成DB
将memory信息和mtklog 一起提供过来。

在测试中用procrank查看uss一直在涨,但用dumpsys meminfo查看Native heap和Dalvik Heap,都涨的很慢,Native heap一直没有涨到128M :

用procrank抓取的信息如下,USS已经到达250M:
C:\Windows\System32>adb shell procrank | findstr launcher3
1698 2801540K 368192K 257240K 250476K com.android.launcher3 

用dumpsys抓取的信息如下:
C:\Windows\System32>adb shell dumpsys meminfo 1698
Applications Memory Usage (kB):
Uptime: 12807842 Realtime: 21690528 

** MEMINFO in pid 1698 [com.android.launcher3] **
Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 29363 28016 0 0 78080 70795 7284
Dalvik Heap 7911 7768 0 0 64595 61187 3408
Dalvik Other 43303 43072 0 0
Stack 1244 1244 0 0
Ashmem 155002 154972 0 0
Other dev 4 0 4 0
.so mmap 2307 516 92 0
.apk mmap 1454 0 96 0
.ttf mmap 106 0 28 0
.dex mmap 1112 4 1108 0
.oat mmap 923 0 116 0
.art mmap 3106 2804 24 0
Other mmap 32 8 0 0
EGL mtrack 7989 7989 0 0
GL mtrack 44356 44356 0 0
Unknown 11308 10660 0 0
TOTAL 309520 301409 1468 0 142675 131982 10692

从dump出来的memory info可以看到,总共309520,也即300多M,其中 Ashmem   155002 ,就占了151M,这个应该才是leak的原因。

ashmem是通过mmap分配的内存,于是按照以下方法打开mmap debug机制再复测提供mtklog:

在vendor/mediatek/proprietary/external/aee/config_external/init.aee.customer.rc添加:
on init
export LD_PRELOAD libsigchain.so:libudf.so

重新打包bootimage并下载开机, 用adb输入:
adb shell setprop persist.debug.mmap.program app_process
adb shell setprop persist.debug.mmap.config 0x22002010
adb shell setprop persist.debug.mmap 1
adb reboot

解析再次提供的DB,用工具分析怀疑SoundPoolThread存在内存泄漏:

== mmap泄漏检查 ==
anon mmap: 800848KB

mmap已分配超过200MB (可能存在内存泄露), 以下列出分配最大尺寸和次数的调用栈:
大小: 1040384字节, 已分配: 497次
分配调用栈:
libudf.so mmap() + 138
libc.so __allocate_thread() + 454
libc.so pthread_create() + 586
libutils.so androidCreateRawThreadEtc() + 138
libutils.so androidCreateThreadEtc() + 14
libutils.so android::createThreadEtc() + 150
libutils.so android::Thread::run() + 290
libstagefright_foundation.so android::ALooper::start() + 342
libmediandk.so createAMediaCodec() + 162
libsoundpool.so decode() + 698
libsoundpool.so android::Sample::doLoad() + 890
libsoundpool.so android::SoundPoolThread::doLoadSample() + 46
libsoundpool.so android::SoundPoolThread::run() + 78
== 栈结束 ==

review 相关code并没有发现泄漏点,在backtrace中的ALooper.cpp中对应的内存分配和释放的地方添加log复现,从复现的NE db 显示,这个内存分配了 505 次:

mmap已分配超过200MB (可能存在内存泄露), 以下列出分配最大尺寸和次数的调用栈:
大小: 1040384字节, 已分配: 505次
分配调用栈:
libudf.so mmap() + 138
libc.so pthread_create() + 586
libutils.so androidCreateRawThreadEtc() + 138
libutils.so androidCreateThreadEtc() + 14
libutils.so android::Thread::run() + 290
libstagefright_foundation.so android::ALooper::start() + 390
libmediandk.so 0x0000007FA130DC24() + 162
libsoundpool.so android::Sample::doLoad() + 890
libsoundpool.so android::SoundPoolThread::doLoadSample() + 46
libsoundpool.so android::SoundPoolThread::run() + 78

从 mobilelog 里面,可以看到相应的 Alooper 确实是有 new 505 次
但对应的,其析构方法也是有跑了 505 次,

01-01 02:40:02.063867 2570 2596 D ALooper : 70615 con: AloopConCounter =505, AloopDesConCounter = 504
01-01 02:40:02.063967 2570 2596 D ALooper : 70615 con: LooperThreadConCounter=505, LooperThreadDesConCounter=504 //loopthread 构造方法内打印,第一个是构造次数,第二个是析构次数
01-01 02:40:02.680956 2570 2596 D ALooper : 70615 Descon: LooperThreadConCounter=505, LooperThreadDesConCounter=505
01-01 02:40:02.681063 2570 2596 D ALooper : 70615 Descon: AloopConCounter =505, AloopDesConCounter = 505 //loopthread 析构方法内打印,第一个是构造次数,第二个是析构次数

那么问题来了,为什么工具解析的结果是未释放呢? 从backtrace出发,__allocate_thread() + 454 对应的code:

attr->stack_base = __create_thread_mapped_space(mmap_size, attr->guard_size);

也即提示是attr->stack_base没有释放,查找代码看它会在哪里被mummap。

libutils.so androidCreateRawThreadEtc() + 138 可知是在androidCreateRawThreadEtc()函数中调用pthread_create,该函数对attr的设置如下:

  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

而对于PTHREAD_CREATE_DETACHED属性的线程所占的资源在pthread_exit 时自动释放,pthread_exit()中可以看到attr->stack_base的释放是通过

_exit_with_stack_teardown(thread->attr.stack_base, thread->mmap_size);

这个函数是汇编代码实现的,而mmap debug机制是通过rehook mmap&mummap 来监测内存的分配和释放,所以对于这种是没有办法监测到释放的过程,因此误判成leak。

重新回到分析报告,既然是ashmem占用内存过多,但是奇怪的是mmap泄漏检查显示的backtrace根本就没有与之相关的内存块,将所有mmap的backtrace列出来也没有看到,这又是怎么回事呢?
review mmap debug机制,mmap_debug.c中:

原因在这里,mmap debug机制只会记录flags为MAP_ANONYMOUS匿名映射的backtrace,而ashmem是有名字的,并不会记录,所以打开mmap debug机制对于检查ashmem leak的case并没有作用。

根据DB解析开的PROCESS_MAPS可以看到,/dev/ashmem/MemoryHeapBase占的内存过高,在MemoryHeapBase.cpp中加log打印申请和释放的backtrace。
从复现提供过来mobilelog结合DB解析开的PROCESS_FILE_STATE中的fd可知:
01-01 02:47:37.378898 1568 2358 D MemoryHeapBase: #00 pc 0000000000032220 /system/lib64/libbinder.so (android::MemoryHeapBase::MemoryHeapBase(unsigned long, unsigned int, char const*)+416)
01-01 02:47:37.378974 1568 2358 D MemoryHeapBase: #01 pc 0000000000006814 /system/lib64/libsoundpool.so (android::Sample::doLoad()+84)
01-01 02:47:37.379019 1568 2358 D MemoryHeapBase: #02 pc 0000000000008b08 /system/lib64/libsoundpool.so (android::SoundPoolThread::doLoadSample(int)+48)
01-01 02:47:37.379062 1568 2358 D MemoryHeapBase: #03 pc 0000000000008ba0 /system/lib64/libsoundpool.so (android::SoundPoolThread::run()+80)
01-01 02:47:37.379103 1568 2358 D MemoryHeapBase: #04 pc 0000000000093410 /system/lib64/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+96)
01-01 02:47:37.379144 1568 2358 D MemoryHeapBase: #05 pc 0000000000014fec /system/lib64/libutils.so
01-01 02:47:37.379184 1568 2358 D MemoryHeapBase: #06 pc 00000000000673c8 /system/lib64/libc.so (__pthread_start(void*)+52)
01-01 02:47:37.379224 1568 2358 D MemoryHeapBase: #07 pc 000000000001ed44 /system/lib64/libc.so (__start_thread+16)
这个backtrace申请的 MemoryHeapBase最终都没有释放,review相关代码发现泄漏点正是这里。

根本原因

客制化代码写的有问题,造成sound 的resouce 无法释放。

解决方案

修改代码

你可能感兴趣的:(内存泄漏专题分析)