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:

    [] create_object+0x12c/0x248

    [] kmemleak_alloc+0x88/0xcc

    [] kmem_cache_alloc_trace+0x13c/0x1f4

    [] ion_carveout_heap_map_dma+0x34/0xcc

    [] ion_alloc+0x170/0x3f0

    [] ion_ioctl+0xc0/0x410

    [] do_vfs_ioctl+0x4f4/0x568

    [] sys_ioctl+0x48/0x6c

    [] ret_fast_syscall+0x0/0x48

    [] 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

  [] dump_trace+0x63/0x1eb

  [] show_trace_log_lvl+0x1a/0x2f

  [] show_trace+0x12/0x14

  [] dump_stack+0x16/0x18

  [] object_err+0x143/0x14b

  [] check_object+0x66/0x234

  [] __slab_free+0x239/0x384

  [] kfree+0xa6/0xc6

  [] get_modalias+0xb9/0xf5

  [] dmi_dev_uevent+0x27/0x3c

  [] dev_uevent+0x1ad/0x1da

  [] kobject_uevent_env+0x20a/0x45b

  [] kobject_uevent+0xa/0xf

  [] store_uevent+0x4f/0x58

  [] dev_attr_store+0x29/0x2f

  [] sysfs_write_file+0x16e/0x19c

  [] vfs_write+0xd1/0x15a

  [] sys_write+0x3d/0x72

  [] sysenter_past_esp+0x5f/0x99

  [] 0xb7f7b410

4.     应用的内存简介

4.1.     查看系统内存

可以使用 ddms 来查看系统的内存使用情况 , 是靠读取 /proc/meminfo 来分析出来的框图。

 

4.2.   进程内存查看

      单个进程的内存使用情况可以检查 proc//status

   再具体可以看

   /proc//statm

   /proc//maps

   /proc//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    5068   5068   2119   1476 /system/bin/mediaserver
 1384     436K     436K     248K     236K  procrank
       212K     212K     200K     200K  /init
  753     572    572    171    136 /system/bin/rild
  748     340    340    163    152 /system/bin/sh
  751     388    388    156    140 /system/bin/vold
 1215     148K     148K     136K     136K  /sbin/adbd
  757     352    352    117     92 /system/bin/dbus-daemon
  760     404    404    104     80 /system/bin/keystore
  759     312    312    102     88 /system/bin/installd
  749     288    288     96     84 /system/bin/servicemanager
  752     244    244     71     60 /system/bin/debuggerd

 

详细解释见如下:

Android has a tool called procrank (/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) is the 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 环境

.打开 Eclipse

.选择 Help->Install New Software;

.在 work with 中添加站点: MAT - http://download.eclipse.org/mat/1.3/update-site/

   ( 这个地址可能会变化,但是新的地址可以在官方网站上找到

     http://www.eclipse.org/mat/downloads.php)

 .生成 .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 为转换过后的文件。

. . 打开 MAT 

    在 Eclipse 中点击 Windows->Open Perspective->Other->Memory Analysis

.导入 .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         检测内存泄漏

libc.debug.malloc         分配的内存用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)

{

   

#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//maps 文件到 PC 机器

2.       ps

3.       dumpsys    -m >   1.mm

4.       操作手机

5.       dumpsys  -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

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐