android native 代码内存泄露 定位方案

4,931 阅读6分钟
原文链接: mp.weixin.qq.com

android native 代码内存泄露 定位方案

java代码的内存定位,暂时我们先不关注。此篇文章,主要围绕c c++代码的内存泄露。


欢迎留言,交流您所使用的内存泄露定位方案。

c   c++代码,由于其特殊性质,没有虚拟机概念,内存则直接是由用户管理,比如申请,释放,都是需要用户主动去触发,如果用户出现使用了申请,但是用完之后,没有调用释放,则会引起内存泄露。这种叫真正意义的内存泄露,只有重启机子,才能恢复。

相对而已java端的内存泄露,指的是一个应用长期运行,导致相互引用,无法释放,GC没法回收,引起的有效内存越来越小,我们将此现象叫做,内存泄露,通过关闭此应用,重新打开即可恢复内存。因此看来,java内存泄露和c c ++ 的 还是有本质区别的。

java本身的虚拟机里面会关注对象的申请,释放,这些不需要用户直接注,java虚拟机通过管理机制,将调用c c++里面真正的malloc free 方法,封装起来,将java对象的生命周期和malloc free 进行关联,则可以保证在对象不使用的时候,内存紧张时,释放掉不再被引用的对象,GC回收就是在做这件事请。

回到我们这节的主要内容,如何定位我们的c  c++的内存泄露。


00


我们查看代码,发现申请内存的代码位置,在/bionic/libc/里面,此库生成出来有 libc.so libstdc++.so (手机的system/lib/里面)我们看到这里有个目录/bionic/libc/malloc_debug/ ,这里便是我们的malloc的调试源码

按照这里的文档讲解 (有个重点,必须是调试版本,因为需要lib_malloc_debug.so 库的存在) 

此malloc的调试原理是:当系统发现我们有libc.debug.malloc的一些列配置成立时,此时系统会将malloc free 等方法,重新指向到 lib_malloc_debug.so里面的对应实现方法,lib_malloc_debug.so里面的方法,像比较而言,多了一些记录信息,将每次的申请时的地址,堆栈,so等信息记录下来,然后我们需要的时候,则通过工具ddms dump出来,进行分析每个申请的内存,是否正常的释放了,是否出现了内存泄露。

 官方的方案:
  adb shell stop
  adb shell setprop libc.debug.malloc.options backtrace
  adb shell start
 
 然后打开需要检测的apk,然后运行,调试下即可出来。实际是不可行的。。。如此尴尬,我也不想太过麻烦的去找why?我们何不暴力一些,直接修改下,这里我给出我的修改方法:


/bionic/libc/bionic/malloc_common.cpp 修改
static void malloc_init_impl(libc_globals* globals)  方法,将前面的一些判断删掉

然后在/bionic/libc目录 mmm单独编译此模块出来即可

adb shell 加入一些参数 (没去较真,是否需要这个)
echo "libc.debug.malloc.program=app_process">> /system/build.prop
echo "libc.debug.malloc.options=backtrace" >> /system/build.prop
echo "libc.debug.malloc.env_enabled=1" >> /system/build.prop
echo "libc.debug.malloc=1" >> /system/build.prop
将我们编译出来的libc.so libstdc++.so 放入手机

adb remount
adb push 'xxxxxx/system/lib/libc.so' /system/lib
adb push xxxxxx/system/lib/libstdc++.so' /system/lib
adb reboot
然后我们重启手机,运行我们的测试demo,这里为jnidemo
我们如何来验证是否成功的debug malloc呢?
mt:/ # ps | grep jnidemo
u0_a155   13112 343   821920 45124 SyS_epoll_ a695c8a8 S com.example.jnidemo
mt:/ # cat /proc/13112/maps | grep malloc_debug
a718a000-a71ae000 r-xp 00000000 103:08 1400      /system/lib/libc_malloc_debug.so
a71af000-a71b0000 r--p 00024000 103:08 1400      /system/lib/libc_malloc_debug.so
a71b0000-a71b1000 rw-p 00025000 103:08 1400      /system/lib/libc_malloc_debug.so
mt:/ #
如果出现了这个/system/lib/libc_malloc_debug.so 则说明内存检测调试成功了。

我们继续来操作,找到我们电脑home目录下的隐藏文件
/home/user/.android
在里面的ddms.cfg文件下加入一行
native=true
加入这句之后,我们的eclipse的独立ddms则会出现native heap显示。
找到eclipse的sdk目录下的/sdk/tools 里面的ddms打开。(记得要关闭其他占用adb的端口进程)

选择我们的进程,然后右侧选择native heap ,点击snapshot current native Heap usage,则会显示出来当前进程申请的c内存信息。

我们通过这个,可以看到某个库的某个位置申请了多大空间,然后我们多次操作,对比申请的空间,从而找到我们的内存泄露点。

01

我们这里演示下如何找到对应的申请空间代码位置:
从上面的图可以看到libtest_jni.so的位置,方法位置8cf84c54 申请大小为100
于是我们需要找下8cf84c54 具体指的哪个方法,具体操作为:
使用cat /proc/pid/maps | grep libtest_jni.so

看到8cf84c54 落在这里的地址为8cf84000 - 8cf87000 中间,再看后面的r-xp 这里有执行位。p私有位
于是我们计算8cf84c54(代码的具体位置) -8cf84000(so的加载起始位置) =0xc54(so库中对应方法的地址)
我们使用addr2line找到这个地址的代码,这里可以看到是XXXXXXXtest_jni.c 的13行,具体为,还是这个图:

找到代码:

我们这里看到 malloc 申请的大小为 100字节  代码位置为13行,我们一直在申请,没有释放过,如上验证了c   c++  内存问题,可以通过此方案进行调试,定位内存泄露问题。

02

综上演示了一次查找,定位c代码申请空间的位置代码,如果发现某个过程的meminfo信息出来的native heap一直增大,我们则可以使用这个调试手段,进行定位,一般尽量定位在跟自己app关联比较大的方法里面。

这里有个小问题,按照ddms这个工具的本身意图,当我们配置好addr2line之后,配置好符号查找位置后,应该自动会解析成符号,而不是地址。但是这里老是提示addr2line工具找不到,很是崩溃,无语,所以才有了上面的手动解析地址到方法的手段。
不过话说回来,这样子不是更学到了内容,还是值得高兴的事情。

一些参考文档:

java内存泄露

http://www.open-open.com/lib/view/open1432102426271.html

http://www.open-open.com/lib/view/open1425993728107.html

LeakCanary

http://www.open-open.com/lib/view/open1453976808761.html

native内存泄露

http://blog.csdn.net/u011280717/article/details/51820268