一次Android平台native heap内存泄露的追查过程

本文记录了一次解决Android平台nativeheap内存泄露的过程。通过修改libcdebug并利用heapsnap工具,最终定位并修复了两个主要的内存泄露点。

一次过程Android平台native heap内存泄露的追查过程

前段时间有个客户报了个问题:他们的一个视频播放app在我们的Android 6.0 SDK上出现内存泄露,我用showmap查了下,该app在长时间播放视频过程中出现native heap内存的持续泄露。

原以为只要把libc debug开关打开了,就能很容易查出问题点,却没想到碰到了一些意想不到的情况。

libc debug开关使能之后,在我们的Android6.0 SDK上执行所有命令及程序都会异常崩溃,系统跑不起来了

是的,通过”setprop libc.debug.malloc 1”使能libc debug后,所有命令都跑不了了,客户的app也跑不了了,出现的Fatal信息如下:

215 F DEBUG   : ABI: 'arm'
215 F DEBUG   : pid: 893, tid: 893, name: iptables  >>> /system/bin/iptables <<<
215 F DEBUG   : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x7
614 W NativeCrashListener: Couldn't find ProcessRecord for pid 893
215 F DEBUG   :     r0 ffffffff  r1 0000000d  r2 be97e1a4  r3 00000000
215 E DEBUG   : AM write failed: Broken pipe
215 F DEBUG   :     r4 be97e1a4  r5 0000000d  r6 be97e298  r7 00000000
215 F DEBUG   :     r8 be97e298  r9 00000084  sl b6ed5bbc  fp 00000000
215 F DEBUG   :     ip b6ee606c  sp be97e178  lr b6ecf791  pc b6ecfca2  cpsr 200e0030
215 F DEBUG   : 
215 F DEBUG   : backtrace:
215 F DEBUG   :     #00 pc 00077ca2  /system/lib/libc++.so
215 F DEBUG   :     #01 pc 0007778d  /system/lib/libc++.so
215 F DEBUG   :     #02 pc 0007742d  /system/lib/libc++.so
215 F DEBUG   :     #03 pc 00048a3f  /system/lib/libc++.so (__gxx_personality_v0+290)
215 F DEBUG   :     #04 pc 0001e384  /system/bin/iptables (__gnu_Unwind_Backtrace+160)
215 F DEBUG   :     #05 pc 0001ecb4  /system/bin/iptables (___Unwind_Backtrace+20)

异常发生在libc++库中,头大,查了半天没找到原因。

试用了下valgrind,发现速度奇慢,且无法用在客户app上,用valgrind加载其它系统自带的app也是跑不起来,果断放弃。

还是想办法把Android自带的libc debug用起来。正向查不出来,那就曲线救国吧。

我修改Android5.1 SDK上面的libc++源码,添加必要函数后编译完成,替换掉样机上原来的libc++.so(样机上跑的是Android6.0系统),居然能工作正常,而且打开libc debug开关后,执行命令也不会崩溃了。

使能libc debug后,打开app,结果又出状况了…

libc debug使能后,客户app无法工作了

在没有打开libc debug时候,客户的app是能正常工作的,但通过以下命令使能libc debug之后:

setprop libc.debug.malloc 1
stop
start

然后打开客户app,一播放视频就会异常退出,异常LOG如下:

ABI: 'arm'
pid: 4011, tid: 4104, name: AsyncTask #1  >>> com.hikvision.stabilitytesttool <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x59a70ecf
    r0 00000001  r1 00004080  r2 59a70ecf  r3 00000008
    r4 00004080  r5 9edf3b60  r6 00000080  r7 00000000
    r8 9edf361c  r9 9edf36e0  sl 80000000  fp 00000005
    ip 9f488158  sp 9edf34f0  lr 9f651971  pc 9f6514d8  cpsr 240b1c30

backtrace:
    #00 pc 0027e4d8  /data/app/com.hikvision.stabilitytesttool-1/lib/arm/libPlayCtrl.so
    #01 pc 0027e96d  /data/app/com.hikvision.stabilitytesttool-1/lib/arm/libPlayCtrl.so
    #02 pc 0027eb61  /data/app/com.hikvision.stabilitytesttool-1/lib/arm/libPlayCtrl.so
    #03 pc 002722d5  /data/app/com.hikvision.stabilitytesttool-1/lib/arm/libPlayCtrl.so
    #04 pc 00008a03  /system/lib/libc_malloc_debug_leak.so (_Unwind_Backtrace+130)
    #05 pc 000060ef  /system/lib/libc_malloc_debug_leak.so
    #06 pc 00006b69  /system/lib/libc_malloc_debug_leak.so (leak_malloc+84)
    #07 pc 00272851  /data/app/com.hikvision.stabilitytesttool-1/lib/arm/libPlayCtrl.so (_Znwj+12)
    #08 pc 000b51bf  /data/app/com.hikvision.stabilitytesttool-1/lib/arm/libPlayCtrl.so (_ZN9CRenderer16InitVideoDisplayEi+102)
    #09 pc 000b4d61  /data/app/com.hikvision.stabilitytesttool-1/lib/arm/libPlayCtrl.so (_ZN9CRenderer14SetVideoWindowEPvii+122)

因为打开了libc debug,因此在malloc时候会保存内存分配的调用路径,从上面异常backtrace可以看到在获得该调用路径时候崩溃了。崩溃点发生在客户的库文件中。

由于没有客户app源码,无法正向去查这个异常崩溃问题,所以,依然走曲线救国的道路…

我修改libc,在反查调用路径时候,把客户app中会导致异常的线程都过滤掉,之后,客户app终于能够工作了 ^_^

使用heapsnap工具获得heap的backtrace信息

通过前面的更改,终于能用libc debug了,接下来就是用heapsnap工具抓取客户app的heap详细信息了。
heapsnap工具可以参考这篇:《HeapSnap工具原理及其应用》
以查明内存泄露的地方,很快查到了两个异常的内存泄露点:

size       12, dup 214822
....
size       12, dup 53687, 0xb4ce3e20, 0xb6d26fa8, 0xb4bcdb8a, 0xb4bbf944, 0xb4bc78e0, 0xb4ba2a96, 0xb4acb512, 0xb49732f4, 0xb6e2770c, 0xb6e27780, 0xb6bb4aee, 0xb6bad77a, 0xb6bb2e8c, 0xb6bc3cf2, 0xb5c71588, 0xb5c70200, 0xb5c73516, 0xb5bf6e1c, 0xb5bf6c84, 0xb5bf8c4e, 0xb5bf768e, 0xb6ec8854, 0xb6cb27e6, 0xb6c8a332
          #00  pc 00006e20  /system/lib/libc_malloc_debug_leak.so (leak_malloc+83)
          #01  pc 00049fa8  /system/lib/libc++.so (operator new(unsigned int)+15)
          #02  pc 0034cb8a  /system/lib/libart.so (art::ThreadList::Register(art::Thread*)+257)
          #03  pc 0033e944  /system/lib/libart.so (art::Thread::Init(art::ThreadList*, art::JavaVMExt*, art::JNIEnvExt*)+487)
          #04  pc 003468e0  /system/lib/libart.so (art::Thread::Attach(char const*, bool, _jobject*, bool)+135)
          #05  pc 00321a96  /system/lib/libart.so (art::Runtime::AttachCurrentThread(char const*, bool, _jobject*, bool)+13)
          #06  pc 0024a512  /system/lib/libart.so
          #07  pc 000f22f4  /system/lib/libart.so
          #08  pc 0008870c  /system/lib/libandroid_runtime.so (android::JNISurfaceTextureContext::getJNIEnv(bool*)+47)
          #09  pc 00088780  /system/lib/libandroid_runtime.so (android::JNISurfaceTextureContext::onFrameAvailable(android::BufferItem const&)+11)
          #10  pc 00032aee  /system/lib/libgui.so (android::ConsumerBase::onFrameAvailable(android::BufferItem const&)+93)
          #11  pc 0002b77a  /system/lib/libgui.so (android::BufferQueue::ProxyConsumerListener::onFrameAvailable(android::BufferItem const&)+45)
          #12  pc 00030e8c  /system/lib/libgui.so (android::BufferQueueProducer::queueBuffer(int, android::IGraphicBufferProducer::QueueBufferInput const&, android::IGraphicBufferProducer::QueueBufferOutput*)+1803)
          #13  pc 00041cf2  /system/lib/libgui.so (android::Surface::queueBuffer(ANativeWindowBuffer*, int)+641)
          #14  pc 0006b588  /system/lib/libstagefright.so (android::ACodec::BaseState::onOutputBufferDrained(android::sp<android::AMessage> const&)+483)
          #15  pc 0006a200  /system/lib/libstagefright.so (android::ACodec::BaseState::onMessageReceived(android::sp<android::AMessage> const&)+619)
          #16  pc 0006d516  /system/lib/libstagefright.so (android::ACodec::ExecutingState::onMessageReceived(android::sp<android::AMessage> const&)+481)
          #17  pc 0000be1c  /system/lib/libstagefright_foundation.so (android::AHierarchicalStateMachine::handleMessage(android::sp<android::AMessage> const&)+63)
          #18  pc 0000bc84  /system/lib/libstagefright_foundation.so (android::AHandler::deliverMessage(android::sp<android::AMessage> const&)+15)
          #19  pc 0000dc4e  /system/lib/libstagefright_foundation.so (android::AMessage::deliver()+53)
          #20  pc 0000c68e  /system/lib/libstagefright_foundation.so (android::ALooper::loop()+221)
          #21  pc 00010854  /system/lib/libutils.so (android::Thread::_threadLoop(void*)+111)
          #22  pc 000417e6  /system/lib/libc.so
          #23  pc 00019332  /system/lib/libc.so

上述信息中,size后面数值是申请内存的大小,dup后面数值是申请的次数。
这两处泄露点都是12字节的内存泄露,其中一个申请了214822次,另一个申请了53687次。前面一处的heap信息没有输出backtrace,是因为我们修改libc把异常线程过滤掉的缘故;而后面的heap信息中就带有backtrace了。

通过解读这个backtrace,可以看到libart库中的art::ThreadList::Register函数中申请了内存,但没有释放掉。

最后

最后,修改libcxx/include/list中的remove函数,重新编译art推送到样机上,再用heapsnap抓取heap信息,前面发现的那两处heap泄露点都已经不存在。至此,内存泄露问题终于解决了。

如果没有libc debug,还有哪些方式可以查这个问题呢?

Java HeapNative Heap内存占用一直变化的原因主要有以下几点: ### Java Heap内存占用变化原因 - **对象创建与销毁**:在应用运行过程中,会不断地创建新的Java对象,这些对象会被分配到Java Heap中,从而使Java Heap的内存占用增加。当这些对象不再被引用时,垃圾回收机制会将它们标记为可回收对象,并在合适的时机进行回收,此时Java Heap的内存占用就会减少。例如,在一个循环中不断创建新的字符串对象,Java Heap的内存占用会持续上升;当循环结束且这些对象不再被引用后,垃圾回收器会回收这些对象,内存占用下降[^1]。 ```java for (int i = 0; i < 1000; i++) { String str = new String("test"); // 更多代码 } // 一段时间后,垃圾回收可能会回收这些字符串对象 ``` - **垃圾回收机制**:Java的垃圾回收机制是自动进行的,但它的触发时机和执行效率会影响Java Heap的内存占用。当垃圾回收器启动时,会暂停应用的部分线程,对Java Heap中的对象进行标记和清理。不同的垃圾回收算法和策略会导致不同的内存占用变化模式。例如,当Java Heap的使用量达到一定阈值时,会触发垃圾回收,回收后内存占用会下降;如果垃圾回收不及时,内存占用会持续升高[^1]。 - **业务逻辑需求**:应用的业务逻辑决定了对象的创建和使用频率。例如,在一个图片处理应用中,当用户打开一张大图片时,应用会创建大量的图像对象,Java Heap的内存占用会迅速增加;当图片处理完成或关闭图片时,这些对象可能会被释放,内存占用减少。 ### Native Heap内存占用变化原因 - **本地库的使用**:许多Android应用会使用本地库(如C、C++库)来实现一些高性能的功能。这些本地库在运行时会在Native Heap中分配内存。当本地库加载、执行操作或释放资源时,Native Heap的内存占用会相应地发生变化。例如,一个使用OpenCV库进行图像识别的应用,在加载OpenCV库和处理图像时,Native Heap的内存占用会增加;处理完成后,释放相关资源,内存占用减少[^1]。 - **内存泄漏**:由于Native Heap没有自动的垃圾回收机制,内存泄漏Native Heap中更容易发生。如果在本地代码中分配了内存但没有正确释放,随着应用的运行,Native Heap的内存占用会不断增加。例如,在C++代码中使用`new`分配了内存,但忘记使用`delete`释放,会导致内存泄漏,使Native Heap的内存占用持续上升[^1]。 ```cpp void memoryLeakFunction() { int* ptr = new int[100]; // 忘记释放内存 } ``` - **系统资源竞争**:Android系统中的多个应用和进程会共享系统的内存资源。当系统内存紧张时,操作系统可能会对应用的Native Heap内存进行调整,导致应用的Native Heap内存占用发生变化。例如,当其他应用占用大量内存时,当前应用的Native Heap可能会被压缩,内存占用减少。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值