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