内存泄露分析

来源:互联网 发布:天穹网络老板 编辑:程序博客网 时间:2024/06/01 20:26

Native 内存泄漏

第一部分:通过malloc分配内存泄漏

对于native内存泄漏,比较常见的一类是C堆内存泄漏,即调用malloc申请的内存没有及时释放造成的内存泄漏。

调试方法

1.打开malloc debug15机制复现问题

如何开启malloc debug15请参考:

MediaTek On-Line> QuickStart> 踩内存专题分析native踩内存调试方法

2.检查是否存在内存泄漏。

打开mtklogger,不断通过adb shell procrank -u > procrank.txt查看当前系统内存的使用情况。当看到被监控的进程占用内存(USS字段)超过了正常值很多,则可能存在内存泄漏。

3. 得到该进程内存使用分布情况($pid为被监控进程pid)
(1)adb shell dumpsys meminfo$pid >meminfo_$pid.txt

(2)adb shell procmem $pid >procmem_$pid.txt

(3)adb shellkill -11 $pid>生成DB

 分析问题

 复现问题后使用E-Consulter分析,分析完coredump会提供分析报告。如果存在超过128M以上的泄漏,那么分析报告会提示如下:

 == C堆检查 ==
分配器: dlmalloc,最多允许使用: 4GB,最多使用: 188MB,当前使用: 166MB,泄露阈值: 128MB,调试等级: 15
 
该堆已分配超过128MB (可能存在内存泄露),以下列出分配最大尺寸和次数的调用栈:
大小: 1184字节,已分配: 134604
分配调用栈:
 libc_malloc_debug_leak.so 0x40D5ECD4() + 16
 libc_malloc_debug_leak.so leak_malloc() + 43
 libc.so malloc() + 18
 libsqlite.so sqlite3MemMalloc() + 14<external/sqlite/dist/sqlite3.c:15298>
 libsqlite.so mallocWithAlarm() + 86 <external/sqlite/dist/sqlite3.c:18862>
 libsqlite.so sqlite3Malloc() + 16<external/sqlite/dist/sqlite3.c:18895>
 ......
 ==
栈结束 ==
 
大小: 1184字节,已分配: 5149
分配调用栈:
 libc_malloc_debug_leak.so 0x40D5ECD4() + 16
 libc_malloc_debug_leak.so leak_malloc() + 43
 libc.so malloc() + 18
 libsqlite.so sqlite3MemMalloc() + 14<external/sqlite/dist/sqlite3.c:15298>
 libsqlite.so mallocWithAlarm() + 86<external/sqlite/dist/sqlite3.c:18862>
 ......
 ==
栈结束 ==
 
大小: 1048584字节,已分配: 1
分配调用栈:
 libc_malloc_debug_leak.so 0x40D5ECD4() + 16
 libc_malloc_debug_leak.so leak_malloc() + 43
 libc.so malloc() + 18
 libsqlite.so sqlite3MemMalloc() + 14<external/sqlite/dist/sqlite3.c:15298>
 libsqlite.so mallocWithAlarm() + 86<external/sqlite/dist/sqlite3.c:18862>
 libsqlite.so sqlite3Malloc() + 16<external/sqlite/dist/sqlite3.c:18895>
 ......
 ==
栈结束 ==

 剩下就是检查代码逻辑,修复问题了.

第二部分:通过mmap分配内存泄漏

native进程申请内存的方法很多,除了可以通过传统的堆分配函数malloc分配内存,还可以直接通过mmap映射内存。对于通过mmap分配的内存泄漏该如何调试呢?

调试方法

1.打开mmap debug机制复现

vendor/mediatek/proprietary/external/aee/config_external/init.aee.customer.rc添加:

  on init

    export LD_PRELOAD libsigchain.so:libudf.so:...

"..."是原先LD_PRELOAD的内容,如果原先LD_PRELOAD没有内容,则":..."就不需要了。

重新打包bootimage并下载开机,adb输入:

adb shell setprop persist.debug.mmap.programapp_process
adb shell setprop persist.debug.mmap.config 0x22002010
adb shell setprop persist.debug.mmap 1
adb reboot

注意:此类问题必须要抓到coredump,如何开启coredump请参考FAQ[FAQ18022]L版本及之后的版本如何开启directcoredump? 

2.检查是否存在内存泄漏。

打开mtklogger,不断通过adb shell procrank -u > procrank.txt查看当前系统内存的使用情况。当看到被监控的进程占用内存(USS字段)超过了正常值很多,则可能存在内存泄漏。

3. 得到该进程内存使用分布情况($pid为被监控进程pid)
(1)adb shell dumpsys meminfo$pid >meminfo_$pid.txt

(2)adb shell procmem $pid >procmem_$pid.txt

(3)adb shellkill -11 $pid>生成DB

分析问题

 复现问题后使用E-Consulter分析,分析完coredump会提供分析报告。如果存在超过200M以上的泄漏,那么分析报告会提示如下:

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

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.soandroid::ALooper::start() + 390
 libmediandk.so 0x0000007F78EE4C24() +162
 libsoundpool.so android::Sample::doLoad()+ 890
 libsoundpool.soandroid::SoundPoolThread::doLoadSample() + 46
 libsoundpool.soandroid::SoundPoolThread::run() + 78
 ==
栈结束 ==

大小:104857600字节,已分配:1
分配调用栈:

libudf.so mmap() + 138
 libwebviewchromium_loader.so0x0000007F72EA6FBC() + 38
 libart.so 0x0000007F7DA33E10() + 150
 ...... 0x00000000063FFFFA()
 ==
栈结束 ==

大小: 1069056字节,已分配: 61
分配调用栈:
 libudf.so mmap() + 138
 libc.so pthread_create() + 586
 libart.soart::Thread::CreateNativeThread() + 406
 libart.so 0x0000007F7DA33E10() + 150
 /dev/ashmem/dalvik-zygote space(deleted) 0x000000007226DFB2()
 ==
栈结束 ==

  剩下就是检查代码逻辑,修复问题了。

第三部分:内存回收机制

kernel的内存泄漏和native类似,不过原生的kernel就有集成内存回收和调试的功能。
kernel
设计的原则是不到万不得已就不做任何事情,比如内存尽量分配出去使用,实在没有内存才启动回收。

网络有些文章讲解内存回收机制,可以参考:

  • linux kernel内存回收机制

内存回收机制

内存水位

对于内存是否回收是针对每个zone考量的。而考量的参数有high、low和min,各个zone各一套,这3个参数的关系如下:

  • high > low > min
  • min以下的内存属于系统的保留内存,用以满足特殊使用,不会分配给用户态的申请

当内存可用量小于low时,kernel开始启动内核线程kswapd进行内存回收,当内存可用量降至min时,kernel直接进行直接回收(direct reclaim),即直接在应用程序的进程上下文中进行回收,再用回收上来的空闲页满足内存申请,因此会阻塞应用程序,带来一定的响应延迟,如果回收不到则会启动oom-killer通过杀程序回收内存,如果还是回收失败则会触发OOM。

kernel要么不回收内存,一旦开始回收内存,它将持续回收一直到可用的内存大于high。参考下图:


kswapd

kswapd是内存回收进程,且每个zone有一个,会定期监控和回收内存。

OOM killer

实在要不到内存时,则启动oom killer,就是通过杀死比较肥的进程来回收内存。

如何评判要杀哪个进程呢?就是要给进程打分,根据分数排行,选择杀哪个进程,这里有篇文章讲解,请参考:

  • OOM相关的参数

隐含的规则

too small to fail

GFP_KERNEL申请低阶(< 3连续内存(8个连续页)分配的话,就不会返回NULL,如果不够内存,会通过回收机制回收,如果回收不到直接panic

第四:内存泄漏判断方法

检测

OOM killer被启动时,会打印系统内存使用情况,有zone的情况,有swap的情况,等等。

以下举例OOM killerlog

[ 92.101342] (4)[2246:Binder_1]Binder_1 invokedoom-killer: gfp_mask=0x200da, order=0, oom_score_adj=-705
[ 92.101379] (4)[2246:Binder_1]CPU: 4 PID: 2246 Comm: Binder_1 Tainted: P W O3.18.22+ #1
[ 92.101384] (4)[2246:Binder_1]Hardware name: MT6797 (DT)
[ 92.101390] (4)[2246:Binder_1]Call trace:
[ 92.101405] (4)[2246:Binder_1][] dump_backtrace+0x0/0x15c
[ 92.101412] (4)[2246:Binder_1][] show_stack+0x10/0x1c
[ 92.101423] (4)[2246:Binder_1][] dump_stack+0x74/0xb8
[ 92.101432] (4)[2246:Binder_1][] dump_header.isra.14+0x78/0x1bc
[ 92.101439] (4)[2246:Binder_1][] oom_kill_process+0x2e4/0x468
[ 92.101446] (4)[2246:Binder_1][] out_of_memory+0x2fc/0x338
[ 92.101454] (4)[2246:Binder_1][] __alloc_pages_nodemask+0x8cc/0x8e0
[ 92.101462] (4)[2246:Binder_1][] read_swap_cache_async+0x10c/0x1f8
[ 92.101468] (4)[2246:Binder_1][] swapin_readahead+0x13c/0x1a8
[ 92.101476] (4)[2246:Binder_1][] handle_mm_fault+0x65c/0xa5c
[ 92.101485] (4)[2246:Binder_1][] do_page_fault+0x234/0x348
[ 92.101491] (4)[2246:Binder_1][] do_mem_abort+0x38/0x9c
......
[ 92.101571] (4)[2246:Binder_1]Mem-Info:
[ 92.101577] (4)[2246:Binder_1]DMA per-cpu:
[ 92.101582] (4)[2246:Binder_1]CPU 4: hi: 186, btch: 31 usd: 16
[ 92.101587] (4)[2246:Binder_1]CPU 5: hi: 186, btch: 31 usd: 34
[ 92.101592] (4)[2246:Binder_1]CPU 6: hi: 186, btch: 31 usd: 141
[ 92.101597] (4)[2246:Binder_1]CPU 7: hi: 186, btch: 31 usd: 33
[ 92.101603] (4)[2246:Binder_1]Movable per-cpu:
[ 92.101608] (4)[2246:Binder_1]CPU 4: hi: 186, btch: 31 usd: 0
[ 92.101614] (4)[2246:Binder_1]CPU 5: hi: 186, btch: 31 usd: 0
[ 92.101619] (4)[2246:Binder_1]CPU 6: hi: 186, btch: 31 usd: 30
[ 92.101625] (4)[2246:Binder_1]CPU 7: hi: 186, btch: 31 usd: 30
[ 92.101635] (4)[2246:Binder_1]active_anon:429 inactive_anon:395isolated_anon:0
[ 92.101635] active_file:334 inactive_file:480 isolated_file:0
[ 92.101635] unevictable:606889 dirty:0 writeback:0 unstable:0
[ 92.101635] free:1939 slab_reclaimable:4521 slab_unreclaimable:11934
[ 92.101635] mapped:764 shmem:9 pagetables:4640 bounce:0
[ 92.101635] free_cma:226
[ 92.101654] (4)[2246:Binder_1]DMA free:6776kB min:4996kB low:25820kBhigh:27068kB active_anon:1324kB inactive_anon:868kB active_file:872kBinactive_file:1264kB unevictable:1907520kB isolated(anon):0kBisolated(file):0kB present:2418920kB managed:2338728kB mlocked:1901900kBdirty:0kB writeback:0kB mapped:2832kB shmem:20kB slab_reclaimable:18084kBslab_unreclaimable:47736kB kernel_stack:18288kB pagetables:18560kB unstable:0kBbounce:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:28744all_unreclaimable? yes
[ 92.101660] (4)[2246:Binder_1]lowmem_reserve[]: 0 0 512
[ 92.101682] (4)[2246:Binder_1]Movable free:980kB min:1116kB low:5780kBhigh:6060kB active_anon:392kB inactive_anon:712kB active_file:464kBinactive_file:656kB unevictable:520036kB isolated(anon):0kB isolated(file):0kBpresent:524288kB managed:524288kB mlocked:520036kB dirty:0kB writeback:0kBmapped:224kB shmem:16kB slab_reclaimable:0kB slab_unreclaimable:0kBkernel_stack:0kB pagetables:0kB unstable:0kB bounce:0kB free_cma:904kBwriteback_tmp:0kB pages_scanned:16848 all_unreclaimable? yes
[ 92.101687] (4)[2246:Binder_1]lowmem_reserve[]: 0 0 0
[ 92.101698] (4)[2246:Binder_1]DMA: 107*4kB (EMR) 21*8kB (UER) 10*16kB (UR)6*32kB (R) 3*64kB (R) 1*128kB (R) 0*256kB 0*512kB 1*1024kB (R) 0*2048kB1*4096kB (R) = 6388kB
[ 92.101742] (4)[2246:Binder_1]Movable: 239*4kB (C) 38*8kB (C) 6*16kB (C)4*32kB (C) 1*64kB (C) 0*128kB 1*256kB (C) 0*512kB 0*1024kB 0*2048kB 0*4096kB =1804kB
[ 92.101784] (4)[2246:Binder_1]2636 total pagecache pages
[ 92.101789] (4)[2246:Binder_1]286 pages in swap cache
[ 92.101794] (4)[2246:Binder_1]Swap cache stats: add 206768, delete 206482,find 8339/19618
[ 92.101799] (4)[2246:Binder_1]Free swap = 970128kB
[ 92.101804] (4)[2246:Binder_1]Total swap = 1431504kB
[ 92.101808] (4)[2246:Binder_1]735802 pages RAM
[ 92.101813] (4)[2246:Binder_1]0 pages HighMem/MovableOnly
[ 92.101817] (4)[2246:Binder_1]20048 pages reserved
[ 92.101822] (4)[2246:Binder_1][ pid ] uid tgid total_vm rss nr_ptes swapentsoom_score_adj name
......
[ 92.102022] (4)[2246:Binder_1][ 422] 0 422 7551 0 13 290 -1000 netd
[ 92.102056] (4)[2246:Binder_1][ 426] 1013 426 110324 0 127 22197 -1000mediaserver
[ 92.102064] (4)[2246:Binder_1][ 427] 0 427 3549 8 5 141 -1000 installd
[ 92.102090] (4)[2246:Binder_1][ 432] 2000 432 3286 0 4 113 -1000 mobile_log_d
[ 92.102319] (4)[2246:Binder_1][ 465] 1000 465 29620 84 47 2022 -1000program_binary_
[ 92.102327] (4)[2246:Binder_1][ 466] 1000 466 17093 0 29 913 -1000 em_svr
[ 92.102336] (4)[2246:Binder_1][ 467] 0 467 3344 3 5 82 -1000 stp_dump3
[ 92.102352] (4)[2246:Binder_1][ 1004] 2000 1004 4507 0 6 156 -1000 emdlogger1
[ 92.102360] (4)[2246:Binder_1][ 1005] 1001 1005 6331 0 10 129 -1000 gsm0710

muxd
[ 92.102368] (4)[2246:Binder_1][ 1025] 2000 1025 4503 0 7 155 -1000 emdlogger3
[ 92.102385] (4)[2246:Binder_1][ 1202] 1001 1202 13363 0 24 555 -1000 mtkrild
[ 92.102422] (4)[2246:Binder_1][ 1762] 10030 1724 559698 1665 241 20279 -705Binder_1
[ 92.102433] (4)[2246:Binder_1][ 1916] 1010 1916 5084 2 8 226 -1000wpa_supplicant
[ 92.102456] (4)[2246:Binder_1][ 2091] 1001 2091 422449 0 123 11588 -705om.mediatek.gba
[ 92.102464] (4)[2246:Binder_1][ 2097] 1001 2097 425070 0 131 11916 -705om.mediatek.ims
[ 92.102511] (4)[2246:Binder_1][ 2159] 1000 2159 423010 0 124 11659 -705atek.nlpservice
[ 92.102526] (4)[2246:Binder_1][ 2179] 1000 2179 445607 0 170 12468 -705com.dolby
[ 92.102533] (4)[2246:Binder_1][ 2612] 1000 2612 6395 0 10 717 -1000volte_stack
[ 92.102598] (4)[2246:Binder_1][ 6219] 2000 6219 608867 606056 1189 179 -1000tewilovesyouy
[ 92.102630] (4)[2246:Binder_1] client( dbg_name) pid size address
[ 92.102635](4)[2246:Binder_1]----------------------------------------------------
[ 92.102647] (4)[2246:Binder_1] ndroid.systemui( gralloc) 1724 142049280xffffffc02a72ff00
[ 92.102654] (4)[2246:Binder_1] m.android.phone( gralloc) 2084 131072000xffffffc058359840
[ 92.102668] (4)[2246:Binder_1] surfaceflinger( gralloc) 402 512327680xffffffc08aed5e40
[ 92.102675] (4)[2246:Binder_1] system_server( gralloc) 1254 131072000xffffffc092df0840
[ 92.102682] (4)[2246:Binder_1] display( from_kernel) 1 86302720xffffffc099a9a240
[ 92.102690] (4)[2246:Binder_1] disp_decouple( from_kernel) 116 62218240xffffffc09a620b40
[ 92.102697] (4)[2246:Binder_1] disp_decouple( from_kernel) 116 62218240xffffffc09a620cc0
[ 92.102705] (4)[2246:Binder_1] disp_decouple( from_kernel) 116 62218240xffffffc09a620e40
[ 92.102710] (4)[2246:Binder_1]----------------------------------------------------
[ 92.102714] (4)[2246:Binder_1]orphaned allocations (info is from last knownclient):
[ 92.102722](4)[2246:Binder_1]----------------------------------------------------
[ 92.102727] (4)[2246:Binder_1] total orphaned 0
[ 92.102731] (4)[2246:Binder_1] total 83005440
[ 92.102736](4)[2246:Binder_1]----------------------------------------------------
[ 92.102742] (4)[2246:Binder_1]7 PID Memory by Page
[ 92.102746] (4)[2246:Binder_1]7============================
[ 92.102751] (4)[2246:Binder_1]7 2084 936
[ 92.102756] (4)[2246:Binder_1]7 4765 5055
[ 92.102761] (4)[2246:Binder_1]7 2237 1078
[ 92.102765] (4)[2246:Binder_1]7 2200 199
[ 92.102770] (4)[2246:Binder_1]7 2013 189
[ 92.102774] (4)[2246:Binder_1]7 1724 1681
[ 92.102779] (4)[2246:Binder_1]7 1254 114
[ 92.102784] (4)[2246:Binder_1]7 465 114
[ 92.102789] (4)[2246:Binder_1]7 402 3903
[ 92.102793] (4)[2246:Binder_1]7============================
[ 92.102798] (4)[2246:Binder_1]7 Total 13269
[ 92.102802] (4)[2246:Binder_1]7============================
[ 92.102809] (4)[2246:Binder_1]Out of memory: Kill process 2003(ek.voicecommand) score 0 or sacrifice child
[ 92.102821] (4)[2246:Binder_1]Killed process 2003 (ek.voicecommand)total-vm:1189876kB, anon-rss:0kB, file-rss:0kB

先讲解各个栏位的含义:

  • active_anon、inactive_anon:native匿名内存。
  • active_file、inactive_file:native file cache,如果OOM一般会释放file cache,基本不用看这个栏位。
  • unevictable:无法回收的内存,包含了mlocked的数据。
  • mlocked:native通过mlock()函数将页面锁住,禁止被swap出去。
  • slab_reclaimable、slab_unreclaimable:kernel slub内存。
  • kernel_stack:内核栈内存,通过这个数据可以算出kernel中存在多少进程。
    • ARM64里进程数 = kernel_stack / 16K,ARM里进程数= kernel_stack / 8K

拿到这样的log,首先判断下是kernel泄漏还是native泄漏。看哪个栏位的值最大。最大的栏位就知道是native还是kernel泄漏了。如果是native泄漏,还可以看下面的进程的rss栏位,最多的那个进程很可能存在native内存泄漏了。

如果是kernel泄漏,要如何调试呢?

第五:调试方法

kmemleak

kernel 2.6.31引入的工具,用于检查内存泄漏。

原理

创建一个进程每10分钟(默认)扫描系统内存然后打印泄漏的内存的信息。

打开方法

config(位于arch/arm64/configs/xxx_defconfigarch/arm/configs/xxx_defconfig)里设定:

CONFIG_DEBUG_KMEMLEAK=y
CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE=1200
# CONFIG_DEBUG_KMEMLEAK_TEST is not set
# CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF is not set

或者用menuconfig配置:

  • Kernel Hacking> Kernel Memory Leak Detector
  • 然后调大Maximum kmemleak early log entires

检查

编译下载后开机查看是否存在/sys/kernel/debug/kmemleak,如果不存在需要手动挂载

mount -t debugfs nodev /sys/kernel/debug/

查看内存泄漏信息

cat /sys/kernel/debug/kmemleak

得到的信息如下:

unreferenced object 0xf9061000 (size 512):
comm "insmod", pid 12750, jiffies 14401507 (age 110.217s)
hex dump (first 32 bytes):
1c 0f 00 00 01 12 00 00 2a 0f 00 00 01 12 00 00 ........*.......
38 0f 00 00 01 12 00 00 bc 0f 00 00 01 12 00 00 8...............
backtrace:
< c10b0001> create_object+0x114/0x1db
< c148b4d0> kmemleak_alloc+0x21/0x3f
< c10a43e9> __vmalloc_node+0x83/0x90
< c10a44b9> vmalloc+0x1c/0x1e
< f9055021> myfunc+0x21/0x23 hello_kernel
< f9058012> 0xf9058012
< c1001226> do_one_initcall+0x71/0x113
< c1056c48> sys_init_module+0x1241/0x1430
< c100284c> sysenter_do_call+0x12/0x22
< ffffffff> 0xffffffff

第六:Native案例分析

题背景

Launchermonkey时存在泄露,2个小时USS65M涨到205

分析过程

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

在测试中用procrank查看uss一直在涨,但用dumpsys meminfo查看Native heapDalvik 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,也即300M,其中 Ashmem   155002 ,就占了151M,这个应该才是leak的原因。

ashmem是通过mmap分配的内存,于是按照以下方法打开mmapdebug机制再复测提供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<bionic/libc/bionic/pthread_create.cpp:155>
libc.so pthread_create() + 586<bionic/libc/bionic/pthread_create.cpp:229>
libutils.so androidCreateRawThreadEtc() + 138<system/core/libutils/Threads.cpp:157>
libutils.so androidCreateThreadEtc() + 14<system/core/libutils/Threads.cpp:296>
libutils.so android::createThreadEtc() + 150<system/core/include/utils/AndroidThreads.h:112>
libutils.so android::Thread::run() + 290<system/core/libutils/Threads.cpp:698>
libstagefright_foundation.so android::ALooper::start() + 342<frameworks/av/media/libstagefright/foundation/ALooper.cpp:123>
libmediandk.so createAMediaCodec() + 162<frameworks/av/media/ndk/NdkMediaCodec.cpp:151>
libsoundpool.so decode() + 698 <frameworks/base/media/jni/soundpool/SoundPool.cpp:537>
libsoundpool.so android::Sample::doLoad() + 890<frameworks/base/media/jni/soundpool/SoundPool.cpp:646>
libsoundpool.so android::SoundPoolThread::doLoadSample() + 46<frameworks/base/media/jni/soundpool/SoundPoolThread.cpp:108>
libsoundpool.so android::SoundPoolThread::run() + 78<frameworks/base/media/jni/soundpool/SoundPoolThread.cpp:86>
==
栈结束 ==

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-0102: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<bionic/libc/bionic/pthread_create.cpp:155>对应的code

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

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

libutils.so androidCreateRawThreadEtc() + 138<system/core/libutils/Threads.cpp:157>可知是在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根本就没有与之相关的内存块,将所有mmapbacktrace列出来也没有看到,这又是怎么回事呢?
review mmap debug
机制,mmap_debug.c:

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

根据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(unsignedlong, 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相关代码发现泄漏点正是这里。

根本原因

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

解决方案

修改代码


原创粉丝点击