1. Introduction
Android对内存的使用包括内存泄漏和内存越界,内存泄漏会导致系统内存减少,最终分配不到内存,这样大的程序就不能运行,甚至系统没有内存而崩溃。Android中kernel和应用程序都可能会有内存泄漏和越界。对于Java代码,在越界的时候虚拟机会加以检查并抛出异常。而对于C/C++代码,越界的时候就悄无声息地让程序出错或crash
2. 内核中的内存泄漏检测
内核中已经内嵌了内存泄漏的代码,编译的时候需要打开配置
代码及帮助位置:
其中kmemcheck是检测内存越界等错误的,目前只支持X86
kernel/Documentation/kmemleak.txt
kernel/Documentation/kmemcheck.txt
kernel/mm/kmemleak.c
kernel/mm/kmemcheck.c
内核配置
CONFIG_DEBUG_KMEMLEAK=y
CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE=1000
其中CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE的大小跟board的kernel porting相关,
有的不需要定义,有的需要定义大一点,可以在kmemleak.c中模块初始化代码中调试.
kmemleak模块初始化成功后,会产生/sys/kernel/debug/kmemleak这个文件
操作命令如下:
#su
#echo scan > /sys/kernel/debug/kmemleak扫描泄漏
#cat /sys/kernel/debug/kmemleak 查看泄漏
#echo clear > /sys/kernel/debug/kmemleak清除结果
当出现泄漏后,会有提示,比如
unreferenced object 0xd25f3cc0 (size 64):
comm "Binder_5", pid 1257, jiffies 68676 (age 3105.280s)
hex dump (first 32 bytes):
00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
backtrace:
[<c00fa860>] create_object+0x12c/0x248
[<c0540fd4>] kmemleak_alloc+0x88/0xcc
[<c00f6f10>] kmem_cache_alloc_trace+0x13c/0x1f4
[<c026749c>] ion_carveout_heap_map_dma+0x34/0xcc
[<c0265280>] ion_alloc+0x170/0x3f0
[<c02655c0>] ion_ioctl+0xc0/0x410
[<c010d9b0>] do_vfs_ioctl+0x4f4/0x568
[<c010da6c>] sys_ioctl+0x48/0x6c
[<c000f800>] ret_fast_syscall+0x0/0x48
[<ffffffff>] 0xffffffff
通过backtrace可以看到泄漏的地方是ion_carveout_heap_map_dma,通过看代码发现是
ion_carveout_heap_unmap_dma的时候少释放了内存。
kmemleak的原理这里不作介绍,大致原理扫描是否有指针指向这段内存,没有则认为是泄漏,这也导致有的地方会误报,比如内存重复使用带引用次数的,
int offset = 4;
char *real = kmalloc(size, flag) + offset
kfree(real - offset)
在内核中这种特殊的地方很少,大部分检测出来的都是真的泄漏了。
3. 内核中的内存越界检测
参考: kernel/Documentation/vm/slub.txt
内核配置为使用slub作为内存分配器,slub本身提供了检查越界的接口,如果kernel刚启动就要检查内存破坏,则需要编译的时候配置CONFIG_SLUB_DEBUG_ON=y
否则可以使用slabinfo –d A来打开检查功能,打开后,slub会在内存后面加一些关键字,释放的时候会检查是否被破坏,如果破坏了,check_bytes_and_report中print一个警告,
可以修改check_bytes_and_report后面部分的代码,在debug版本中加入panic让系统死机来报告内存越界错误。
static int check_bytes_and_report(struct kmem_cache *s, struct page *page,
u8 *object, char *what,
u8 *start, unsigned int value, unsigned int bytes)
{
u8 *fault;
u8 *end;
fault = memchr_inv(start, value, bytes);
if (!fault)
return 1;
end = start + bytes;
while (end > fault && end[-1] == value)
end--;
slab_bug(s, "%s overwritten", what);
printk(KERN_WARN "INFO: 0x%p-0x%p. First byte 0x%x instead of 0x%x\n",
fault, end - 1, fault[0], value);
print_trailer(s, page, object);
restore_bytes(s, what, value, fault, end);
return 0;
}
比如显示如下:
BUG kmalloc-8: Redzone overwritten
--------------------------------
INFO: 0xc90f6d28-0xc90f6d2b. First byte 0x00 instead of 0xcc INFO: Slab 0xc528c530 flags=0x400000c3 inuse=61 fp=0xc90f6d58 INFO: Object 0xc90f6d20 @offset=3360 fp=0xc90f6d58 INFO: Allocated in get_modalias+0x61/0xf5 age=53 cpu=1 pid=554
Bytes b4 0xc90f6d10: 00 00 00 00 00 00 00 00 5a 5a 5a 5a 5a 5a 5a 5a ........ZZZZZZZZ Object 0xc90f6d20: 31 30 31 39 2e 30 30 35 1019.005 Redzone 0xc90f6d28: 00 cc cc cc . Padding 0xc90f6d50: 5a 5a 5a 5a 5a 5a 5a 5a ZZZZZZZZ
[<c010523d>] dump_trace+0x63/0x1eb
[<c01053df>] show_trace_log_lvl+0x1a/0x2f
[<c010601d>] show_trace+0x12/0x14
[<c0106035>] dump_stack+0x16/0x18
[<c017e0fa>] object_err+0x143/0x14b
[<c017e2cc>] check_object+0x66/0x234
[<c017eb43>] __slab_free+0x239/0x384
[<c017f446>] kfree+0xa6/0xc6
[<c02e2335>] get_modalias+0xb9/0xf5
[<c02e23b7>] dmi_dev_uevent+0x27/0x3c
[<c027866a>] dev_uevent+0x1ad/0x1da
[<c0205024>] kobject_uevent_env+0x20a/0x45b
[<c020527f>] kobject_uevent+0xa/0xf
[<c02779f1>] store_uevent+0x4f/0x58
[<c027758e>] dev_attr_store+0x29/0x2f
[<c01bec4f>] sysfs_write_file+0x16e/0x19c
[<c0183ba7>] vfs_write+0xd1/0x15a
[<c01841d7>] sys_write+0x3d/0x72
[<c0104112>] sysenter_past_esp+0x5f/0x99
[<b7f7b410>] 0xb7f7b410
4. 应用的内存简介
4.1. 查看系统内存
可以使用ddms来查看系统的内存使用情况,是靠读取/proc/meminfo来分析出来的框图。
4.2. 进程内存查看
单个进程的内存使用情况可以检查 proc/<pid>/status
再具体可以看
/proc/<pid>/statm
/proc/<pid>/maps
/proc/<pid>/smaps
top命令也可以显示VSS和 RSS
- VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)
- RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存)
- PSS - Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
- USS - Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存
一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS
# procrank -h
Usage: procrank [ -W ] [ -v | -r | -p | -u | -h ]
-v Sort by VSS.
-r Sort by RSS.
-p Sort by PSS.
-u Sort by USS.
(Default sort order is PSS.)
-R Reverse sort order (default is descending).
-w Display statistics for working set only.
-W Reset working set of all processes.
-h Display this help screen.
And here is some sample output:
# procrank
PID Vss Rss Pss Uss cmdline
1217 36848K 35648K 17983K 13956K system_server
1276 32200K 32200K 14048K 10116K android.process.acore
1189 26920K 26920K 9293K 5500K zygote
1321 20328K 20328K 4743K 2344K android.process.media
1356 20360K 20360K 4621K 2148K com.android.email
1303 20184K 20184K 4381K 1724K com.android.settings
1271 19888K 19888K 4297K 1764K com.android.inputmethod.latin
1332 19560K 19560K 3993K 1620K com.android.alarmclock
1187 5068K 5068K 2119K 1476K /system/bin/mediaserver
1384 436K 436K 248K 236K procrank
1 212K 212K 200K 200K /init
753 572K 572K 171K 136K /system/bin/rild
748 340K 340K 163K 152K /system/bin/sh
751 388K 388K 156K 140K /system/bin/vold
1215 148K 148K 136K 136K /sbin/adbd
757 352K 352K 117K 92K /system/bin/dbus-daemon
760 404K 404K 104K 80K /system/bin/keystore
759 312K 312K 102K 88K /system/bin/installd
749 288K 288K 96K 84K /system/bin/servicemanager
752 244K 244K 71K 60K /system/bin/debuggerd
详细解释见如下:
Android has a tool calledprocrank (/system/xbin/procrank), which lists out the memory usage of Linux processes in order from highest to lowest usage. The sizes reported per process are VSS, RSS, PSS, and USS.
For the sake of simplicity in this description, memory will be expressed in terms of pages, rather than bytes. Linux systems like ours manage memory in 4096 byte pages at the lowest level.
VSS (reported as VSZ from ps) isthe total accessible address space of a process. This size also includes memory that may not be resident in RAM like mallocs that have been allocated but not written to. VSS is of very little use for determing real memory usage of a process.
RSS is the total memory actually held in RAM for a process. RSS can be misleading, because it reports the total all of the shared libraries that the process uses, even though a shared library is only loaded into memory once regardless of how many processes use it. RSS is not an accurate representation of the memory usage for a single process.
PSS differs from RSS in that it reports the proportional size of its shared libraries, i.e. if three processes all use a shared library that has 30 pages, that library will only contribute 10 pages to the PSS that is reported for each of the three processes. PSS is a very useful number because when the PSS for all processes in the system are summed together, that is a good representation for the total memory usage in the system. When a process is killed, the shared libraries that contributed to its PSS will be proportionally distributed to the PSS totals for the remaining processes still using that library. In this way PSS can be slightly misleading, because when a process is killed, PSS does not accurately represent the memory returned to the overall system.
USS is the total private memory for a process, i.e. that memory that is completely unique to that process. USS is an extremely useful number because it indicates the true incremental cost of running a particular process. When a process is killed, the USS is the total memory that is actually returned to the system. USS is the best number to watch when initially suspicious of memory leaks in a process.
4.3. JAVA虚拟机的堆
Dalvik Heap靠zygote预先加载了类和数据,当zygote fork一个android应用的时候,新的应用得到这个Heap的copy-on-write mapping
5. Debugging Android application memory
5.1. JAVA的内存泄漏和越界
JAVA语言本身对内存越界是敏感的,JAVA中内存越界虚拟机抛出异常。
JAVA的内存泄漏的检测可以通过在eclipse中安装Memory Analyzer Tool(MAT)来插件来进行。
这里只简单说下步骤,需要完整的SDK和eclipse环境
1.打开Eclipse
2.选择 Help->Install New Software;
3.在work with中添加站点:MAT -http://download.eclipse.org/mat/1.3/update-site/
(这个地址可能会变化,但是新的地址可以在官方网站上找到
http://www.eclipse.org/mat/downloads.php)
4.生成.Hprof文件:插入SD卡,在Eclipse中的DDMS中选择要测试的进程,然后点击
Update Heap 和Dump HPROF file两个按钮。
.Hprof 文件会自动保存在SD卡上
把 .hprof文件拷贝到PC上的\ android-sdk-windows\tools目录下。这个由DDMS生成的
文件不能直接在MAT打开,需要转换。进入 android-sdk-windows \tools所在目录,
并输入命令hprof-conv xxxxx.hprof yyyyy.hprof,其中xxxxx.hprof为原始文件,
yyyyy.hprof为转换过后的文件。
5..打开MAT:
在Eclipse中点击Windows->Open Perspective->Other->Memory Analysis
6.导入.hprof文件
在MAT中点击 File->Open File打开刚刚转换而得到的yyyyy.hprof文件,并Cancel掉自
动生成报告,点击Dominator Tree,并按Package分组,选择自己所定义的Package类点右键,在弹出菜单中选择List objects->With incoming references。
这时会列出所有可疑类,右键点击某一项,并选择Path to GC Roots->exclude weak/soft references,会进一步筛选出跟程序相关的所有有内存泄露的类。据此,可以追踪到代码中的某一个产生泄露的类。
5.2. Native的内存泄漏和越界
Android的native内存泄漏可以用valgrind工具,但是这个工具在android里运行太慢,Android里的bionic库提供了内存泄漏的检测方法
见bionic/libc/bionic/malloc_debug_common.c里的注释.
所有的native内存分配函数都在libc库里,为了跟踪,需要使用这个库的特别版版本libc_malloc_debug_leak.so和libc_malloc_debug_qemu.so,可以看看eng或这user-debug版中的/system/lib/下是否有这两个文件,其中libc_malloc_debug_qemu.so是模拟器用的。
在malloc_debug_common.c中的内存调试靠读取属性libc.debug.malloc来控制的,属性值含义如下:
libc.debug.malloc 1 检测内存泄漏
libc.debug.malloc 5 分配的内存用0xeb填充,释放的内存用0xef填充
libc.debug.malloc 10 内存分配打pre-和post- 的桩子,可以检测内存的overruns
libc.debug.malloc 20 SDK模拟器上检测内存用
简单的内存泄漏检测可以通过adb shell写为
#setprop libc.debug.malloc 1
#stop
#start
然后logcat就可以显示内存泄漏,有的server可能stop杀不掉,为了开机就启动,可以在开机的时候就设置属性。
可以修改init.rc,增加setprop libc.debug.malloc 10属性编译烧机,
或者
#adb remount
#vi system/build.prop增加libc.debug.malloc=10属性
除了通过logcat看,也可以配置ddms,通过ddms来看内存泄漏。
可以在~\.android\ ddms.cfg 文件后面添加native=true这样打开ddms就可以看到Native Heap了
点击snapshot current native heap usageke根据提示在symbol search path里输入相应库的符号表路径就行了,需要在启动ddms前把arm-linux-androideabi-addr2linux所在的路径加到PATH环境变量中,如果shell中已经运行过编译android前的. build/enxxx lunch 等操作,则已经加到PATH中去了。
找到泄漏后,根据提示的backtrace地址,可以用NDK中的arm-linux-androideabi-addr2line工具算出对应的函数地址,也可以排列成用tombstone的格式,用stacktrace工具求出函数调用关系。在上图ddms中,在symbol search path里填入编译是的库的路径,
可能可以直接显示出函数的名字。
在libc初始化的时候,注册了atexit函数(__libc_init函数),所以程序退出的时候会调用见native的内存泄漏在程序退出的时候会调用到malloc_debug_fini
void malloc_debug_fini(void)
{
/* We need to finalize malloc iff we implement here custom
* malloc routines (i.e. USE_DL_PREFIX is defined) for libc.so */
#if defined(USE_DL_PREFIX) && !defined(LIBC_STATIC)
if (pthread_once(&malloc_fini_once_ctl, malloc_fini_impl)) {
error_log("Unable to finalize malloc_debug component.");
}
#endif // USE_DL_PREFIX && !LIBC_STATIC
}
对于mediaserver等进程,它一直不退出,就无法显示出泄漏的内存,用kill的方式也不能让atexit注册的退出函数得到调用,所以无法自动显示内存错误,针对server这种情况,有两种方式来处理,这两种方式也适用于其它的可以退出的进程。
第一种是在程序中加入检查泄漏的代码,通过调用get_malloc_leak_info()来查看是否总是不停地增长的。
第二种方法如下: server name为固定的一个server的名字(可以用service list查看)
1. 保存 /proc/<pid>/maps文件到PC机器
2. ps
3. dumpsys <servers name> -m > 1.mm
4. 操作手机
5. dumpsys <servers name> -m > 2.mm
然后比较1.mm和2.mm,看看2比1多了那些内容
内容如下
size 31379, dup 1, 0x400f5dc8, 0x400ae2c6, 0x413afa96, 0x400ab020, 0x400aab74
其中size后面的数字表示分配的大小, dup分配的次数。
通过第1步中的maps文件,可以看到该进程每个库加载的起始地址
比如。0x413afa96是落在camera.sc8825.so的代码段内的,由此计算其指令在文件内的
偏移量,0x413afa96 - 0x41391000 = 0x1ea96。然后用addr2line就可以查看这条指
令对应的源码在哪个文件的哪个位置。
41391000-413e9000 r-xp 00000000 b3:0c
535 /system/lib/hw/camera.sc8825.so
$ arm-linux-androideabi-addr2line -e
out/target/product/sp8825ea/symbols/system/lib/hw/camera.sc8825.so 0x1ea96
如果是应用这则要用dumpheap命令
am dumpheap -n <应用的名字> /data/1.mm
am dumpheap -n <应用的名字> /data/2.mm
上面addr2line的过程可以用脚本去自动实现
也可以随时调用dumpsys meminfo $package_name or $pid 看看消耗内存的趋势
在stop 之后,可以用ps看看哪些server还是没被杀掉,这样的server要调试的要需要主动kill掉,然后再start才可以,或者在开机的时候就设置属性libc_debug_malloc为1