Android的内存/资源泄露,不容易发现,又会引发app甚至是system的一系列问题。

在这里我根据以往碰到的相关问题,总结出了一些检测和修改方法。

*有可能造成memory leak的代码是Framework层的文件,但最终影响了App层的进程;
 所以发现app进程出现memory leak的时候,也要考虑Framework层是否有问题。
*确保一定close资源:try { return; } finally { resource.close(); } 这样即使try块中有return语句,也能保证finally块中的close被执行到。
*resource leak的检查需要启动StrictMode

1.文件IO没有关闭

现象:对app进行重复“进入-退出”的自动化测试,500+次之后出现ANR。

E/StrictMode(  618): A resource was acquired at attached stack trace but never released.
See java.io.Closeable for information on avoiding resource leaks.
E/StrictMode( 618): java.lang.Throwable: Explicit termination method 'close' not called
E/StrictMode( 618): at dalvik.system.CloseGuard.open(CloseGuard.java:184)
E/StrictMode( 618): at java.io.FileInputStream.<init>(FileInputStream.java:80)
E/StrictMode( 618): at java.io.FileReader.<init>(FileReader.java:42)
E/StrictMode( 618): at android.net.http.AndroidHttpClient.fun3(AndroidHttpClient.java:) <- 出错位置
E/StrictMode( 618): at android.net.http.AndroidHttpClient.fun2(AndroidHttpClient.java:)
E/StrictMode( 618): at android.net.http.AndroidHttpClient.fun1(AndroidHttpClient.java:)
E/StrictMode( 618): at com.android.server.location.GpsXtraDownloader.doDownload(GpsXtraDownloader.java:143)
E/StrictMode( 618): at com.android.server.location.GpsXtraDownloader.downloadXtraData(GpsXtraDownloader.java:100)
E/StrictMode( 618): at com.android.server.location.GpsLocationProvider$6.run(GpsLocationProvider.java:1023)
E/StrictMode( 618): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
E/StrictMode( 618): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
E/StrictMode( 618): at java.lang.Thread.run(Thread.java:841)

这种错误不会被容易察觉,但可以避免或在前期找出错误。
小结:
a.不再使用的文件IO要调用close()释放;
b.搜索Log中是否有"release", "leak", "java.io.Closeable"等关键词;
c.BufferedReader br = new BufferedReader(new FileReader(file)); close掉BufferedReader也会隐式close掉FileReader;
d.可能有频繁GC,造成屏幕画面卡顿(如Rotation):

12-06 13:44:13.970  9552  9556 E dalvikvm: 9552(com.test.app) stat: (e) 63 937KB / (c) 11 31MB / (a) 4 13MB / (h) 15MB 20MB 4485KB
12-06 13:44:13.970 9552 9556 D dalvikvm: GC_CONCURRENT freed 8183K, 32% free 23967K/34952K, paused 4ms+25ms, total 150ms
12-06 13:44:13.970 9552 9652 D dalvikvm: WAIT_FOR_CONCURRENT_GC blocked 156ms
12-06 13:44:13.970 9552 9654 D dalvikvm: WAIT_FOR_CONCURRENT_GC blocked 155ms
12-06 13:44:13.970 9552 9658 D dalvikvm: WAIT_FOR_CONCURRENT_GC blocked 156ms
12-06 13:44:13.970 9552 9656 D dalvikvm: WAIT_FOR_CONCURRENT_GC blocked 156ms
12-06 13:44:13.975 9552 9660 D dalvikvm: WAIT_FOR_CONCURRENT_GC blocked 157ms
12-06 13:44:13.980 9552 9561 E StrictMode: A resource was acquired at attached stack trace but never released.
See java.io.Closeable for information on avoiding resource leaks.
12-06 13:44:13.980 9552 9561 E StrictMode: java.lang.Throwable: Explicit termination method 'close' not called
12-06 13:44:13.980 9552 9561 E StrictMode: at dalvik.system.CloseGuard.open(CloseGuard.java:184)
12-06 13:44:13.980 9552 9561 E StrictMode: at java.io.FileInputStream.<init>(FileInputStream.java:80)
12-06 13:44:13.980 9552 9561 E StrictMode: at com.test.app.*(Unknown Source)

2.Cursor没有关闭

现象:自动化测试800+次以后手机reboot

E/StrictMode( 3814): Finalizing a Cursor that has not been deactivated or closed.
       database = /data/data/com.test.app/databases/..., table = null, query = SELECT * FROM table WHERE package = '';
E/StrictMode( 3814): android.database.sqlite.DatabaseObjectNotClosedException:
       Application did not close the cursor or database object that was opened here
E/StrictMode( 3814): at android.database.sqlite.SQLiteCursor.<init>(SQLiteCursor.java:98)
E/StrictMode( 3814): at android.database.sqlite.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:50)
E/StrictMode( 3814): at android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1436)
E/StrictMode( 3814): at android.database.sqlite.SQLiteDatabase.rawQuery(SQLiteDatabase.java:1375)
E/StrictMode( 3814): at com.test.app.*(Unknown Source)

小结:
a.过多cursor会造成Excessive JNI global references,导致system_server的VM aborting
b.Log关键词:"DatabaseObjectNotClosedException"

3. InputChannel fd过多

现象:压力测试反复打开关闭某App 200~300次后出现FC

- ::56.334 E/AndroidRuntime( ): FATAL EXCEPTION: main
- ::56.334 E/AndroidRuntime( ): Process: com.app.foo, PID:
- ::56.334 E/AndroidRuntime( ): java.lang.RuntimeException: Could not read input channel file descriptors from parcel.
- ::56.334 E/AndroidRuntime( ): at android.view.InputChannel.nativeReadFromParcel(Native Method)
- ::56.334 E/AndroidRuntime( ): at android.view.InputChannel.readFromParcel(InputChannel.java:)
- ::56.334 E/AndroidRuntime( ): at android.view.IWindowSession$Stub$Proxy.addToDisplay(IWindowSession.java:)
- ::56.334 E/AndroidRuntime( ): at android.view.ViewRootImpl.setView(ViewRootImpl.java:)
- ::56.334 E/AndroidRuntime( ): at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:)
- ::56.334 E/AndroidRuntime( ): at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:)
- ::56.334 E/AndroidRuntime( ): at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:)
- ::56.334 E/AndroidRuntime( ): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:)
- ::56.334 E/AndroidRuntime( ): at android.app.ActivityThread.access$(ActivityThread.java:)
- ::56.334 E/AndroidRuntime( ): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:)
- ::56.334 E/AndroidRuntime( ): at android.os.Handler.dispatchMessage(Handler.java:)
- ::56.334 E/AndroidRuntime( ): at android.os.Looper.loop(Looper.java:)
- ::56.334 E/AndroidRuntime( ): at android.app.ActivityThread.main(ActivityThread.java:)
- ::56.334 E/AndroidRuntime( ): at java.lang.reflect.Method.invokeNative(Native Method)
- ::56.334 E/AndroidRuntime( ): at java.lang.reflect.Method.invoke(Method.java:)
- ::56.334 E/AndroidRuntime( ): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:)
- ::56.334 E/AndroidRuntime( ): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:)
- ::56.334 E/AndroidRuntime( ): at dalvik.system.NativeStart.main(Native Method)

分析:
App的input event由WindowManagerService管理,WMS会内部创建一个InputManager,两者的连接通过InputChannel来完成。
WMS需要注册两个InputChannel与InputManager连接,其中Server端InputChannel注册在InputManager(SystemServer进程)当中,Client端注册在应用程序主线程中。

InputChannel使用Ashmem匿名共享内存来传递数据,它由一个fd文件描述符指向,同时read端和write端各占用一个fd。
当我们打开一个app时,设置log的tag为"InputTransport",可以看到server(system_server)和client(app)都会construct fd。

 InputTransport Input channel constructed: name='426d6588 com.app.phone/com.app.phone.ActivityWelcome (server)', fd=
InputTransport Input channel constructed: name='426d6588 com.app.phone/com.app.phone.ActivityWelcome (client)', fd=

实际案例:

当startActivity的intent flag被设置为Intent.FLAG_ACTIVITY_MULTIPLE_TASK类似flag的时候,如果没有处理好Activity的生命周期,
就会造成Activity无法回收(RecentApps中大量的task),而所占用的fd指向的ashmem也无法释放(errno = 24),就会造成上述问题。

4. so库内存泄漏

现象:反复打开/关闭某个系统服务(跨层),高概率出现system server挂掉重启

01-19 09:34:47.454  2997  2997 F libc    : Fatal signal 11 (SIGSEGV), code 1, fault addr 0x9e2ec3aa in tid 2997 (system_server)
01-19 09:34:47.579 2337 2337 I DEBUG : Revision: '11'
01-19 09:34:47.579 2337 2337 I DEBUG : ABI: 'arm'
01-19 09:34:47.579 2337 2337 I DEBUG : pid: 2997, tid: 2997, name: system_server >>> system_server <<<
01-19 09:34:47.579 2337 2337 I DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x9e2ec3aa
01-19 09:34:47.609 2337 2337 I DEBUG : r0 be9ceb48 r1 0000000c r2 0000011d r3 00008600
01-19 09:34:47.609 2337 2337 I DEBUG : r4 00000000 r5 00000000 r6 be9ceb48 r7 00000000
01-19 09:34:47.609 2337 2337 I DEBUG : r8 be9ceb48 r9 9e2ec3aa sl 0000011d fp 6f11d000
01-19 09:34:47.609 2337 2337 I DEBUG : ip 0000000c sp be9ceac0 lr b6bedbdd pc b6b8e1b0 cpsr 20000010
01-19 09:34:47.609 2337 2337 I DEBUG :
01-19 09:34:47.609 2337 2337 I DEBUG : backtrace:
01-19 09:34:47.609 2337 2337 I DEBUG : #00 pc 0000b1b0 /system/lib/libz.so (inflate+856)
01-19 09:34:47.609 2337 2337 I DEBUG : #01 pc 0001bbd9 /system/lib/libandroidfw.so (bool inflateToBuffer<BufferReader>(BufferReader&, void*, long, long)+164)
01-19 09:34:47.609 2337 2337 I DEBUG : #02 pc 0001bc5b /system/lib/libandroidfw.so (android::ZipUtils::inflateToBuffer(void*, void*, long, long)+14)
01-19 09:34:47.609 2337 2337 I DEBUG : #03 pc 0000f887 /system/lib/libandroidfw.so (android::_CompressedAsset::getBuffer(bool)+50)
01-19 09:34:47.609 2337 2337 I DEBUG : #04 pc 0008b313 /system/lib/libandroid_runtime.so
01-19 09:34:47.609 2337 2337 I DEBUG : #05 pc 007e9abf /system/framework/arm/boot.oat

分析:
从打印出来的backtrace可以看出,问题发生在framework层调用native层so库,进行inflate app的XML文件的时候出错。
由于出错地方是在Android原生代码(无二次修改)中,并且增加打印log后发现每次出错时XML文件可能来自不同app,所以可以怀疑是其他地方的错误导致的。

我们知道,进程对压缩的XML进行解压、inflate时需要较大的内存空间。这时候遇到的Segment Fault很可能是由于其他地方的memory leak造成的。
而memory leak的地方,就是在system server自身当中或者是它调用过的so库(so库使用的内存也属于调用进程的)。
经过了长时间的分析和排查代码,最后果真在某个库中发现了申请内存和释放内存存在隐患的代码。修改后问题解决。

从这个debug分析的过程,我们可以得到以下经验:

1. 由于C/C++不带有GC,一开始写关于内存申请释放的代码的时候就要细心,否则出问题(涉及到如system server的问题很严重)要耗费大量的人力时间去debug;
2. backtrace只能说明执行到哪里出错了,不能说明出错的根本原因是什么;
3. 分析问题log,还是可以有根据地提出假设再验证(比如看到inflate就假设memory leak)。

相关文章

Android内存泄漏研究

Android内存泄漏分析及调试

Android Memory/Resource Leak总结的更多相关文章

  1. Eclipse用java.util.Scanner时出现Resource leak: 'in' is never closed

    Resource leak: 'in' is never closed : 直译为资源泄漏,‘in’一直没被关闭. 由于声明了数据输入扫描仪(Scanner in),从而获得了配置内存,但是结束时却没 ...

  2. Java关于Resource leak: 's' is never closed的问题

    Resource leak: 's' is never closed的问题 问题:在编写Java时出现了Resource leak: 's' is never closed的问题,也就是对象s下面的波 ...

  3. Android Memory Leak

    线程也是造成内存泄露的一个重要的源头.线程产生内存泄露的主要原因在于线程生命周期的不可控.1.看一下下面是否存在问题 public class ThreadActivity extends Activ ...

  4. [Android Memory] Android内存管理、监测剖析

    转载自:http://blog.csdn.net/anlegor/article/details/23398785 Android内存管理机制: Android内存管理主要有:LowMemory Ki ...

  5. [Android Memory] Android Lint简介(转载)

    英文原文:http://tools.android.com/tips/lint  参照文章:http://blog.csdn.net/thl789/article/details/8037473 转载 ...

  6. [Android Memory] 使用 Eclipse Memory Analyzer 进行堆转储文件分析

    转载地址:http://www.ibm.com/developerworks/cn/opensource/os-cn-ecl-ma/index.html Eclipse Memory Analyzer ...

  7. Android - 资源(resource)转换为String

    资源(resource)转换为String 本文地址: http://blog.csdn.net/caroline_wendy Android建议资源的动态绑定, 即把string写入资源内, 然后运 ...

  8. [Android Memory] 内存分析工具 MAT 的使用

    转载自: http://blog.csdn.net/aaa2832/article/details/19419679 1 内存泄漏的排查方法 Dalvik Debug Monitor Server ( ...

  9. [Android Memory] Shallow Heap大小计算释疑

    转载自:http://blog.csdn.net/sodino/article/details/24186907 查看Mat文档时里面是这么描述Shallow Heap的:Shallow heap i ...

随机推荐

  1. Windows Server 2008 安装好之后的简单配置

    1.禁用密码复杂度 在运行中输入GPEDIT.MSC 打开组策略,找到计算机配置->Windows设置->安全设置->账户策略中的密码策略,将“密码必须符合复杂性要求”设置为禁用即可 ...

  2. C++中指针和引用的选择

    何时使用引用和指针      1. 尽可能使用引用传递参数      2. 尽可能的使用const来保护引用和指针      3. 在可以使用引用的时候不要使用指针      4. 不要试图给引用重新 ...

  3. 使用chrome调试xpath

    使用chrome调试xpath 相信玩过爬虫的都知道一些库,如lxml(python),可以使用xpath方便地对HTML进行提取,但当真正用的时候,问题就来了,想找到一个元素往往要调试好几遍,而且得 ...

  4. -webkit-filter是神马?

    这些滤镜效果最初是用于SVG的,W3C引入到CSS中,然后制定了CSS Filter Effects 1.0,现在webkit率先支持了它.   现在规范中支持的效果有:   - grayscale ...

  5. [转载]node.js express 4.x 安装指南,没有自动配置环境变量的问题

    前几天express 推出了4.0,得知这个消息,自己尝试了一下,突然发现用以前的文档上的操作出现了各种问题.结果只能去看文档,现在在这个给大家分享下4.0版本的安装. 先说下如果需要用express ...

  6. windows中copy命令详解

    功能:将一份文件或者多份文件复制到另一个位置 用法: copy [/D] [/V] [/N] [/Y|/-Y] [/Z] [/A | /B] source [/A | /B] [+ source [/ ...

  7. 'gbk' codec can't encode character

    做爬虫抓取网页,print(html)进行调试,遇到UnicodeEncodeError: 'gbk' codec can't encode character XX in position XX问题 ...

  8. java 执行linux命令

    原文地址: http://blog.csdn.net/xh16319/article/details/17302947 package scut.cs.cwh; import java.io.Inpu ...

  9. U盘安装XP SP3 professional手记

    因为专业应用的软件无法在win7上运行,但在XP上是可以正常使用,所以有想法并有必要重装一下. 说句实在话,之前用U盘安装Windows7真的没有这么麻烦,没想到相同的技术用于安装XP 却是如此折腾人 ...

  10. 深入理解java虚拟机---读后笔记(垃圾回收)

    运行时数据区,主要包括方法区.虚拟机栈.本地方法栈.堆.程序计数器,该部分内存都是线程隔离的. 然后和其交互的有执行引擎.本地库接口,此部分线程之间是可以共享的. 1. 引用计数算法 给对象添加一个引 ...