hehui1860的专栏

从Android出发,终点是软件架构

应用内存泄露问题分析实例

内存泄露,这个应用开发中比较容易出现的问题,由于短时间内难以测出来一般都比较难以发现,只能靠开发者的警觉性来避免,长时间的Monkey测试就成为一个可行的手段。

Monkey测试能给出一个直观的内存变化曲线和日志,GC日志在内存紧张时都会打印出测试进程的内存信息。往往报错信息也会指明是 java.lang.OutOfMemoryError但是报这个异常的地方往往并不是内存泄露的位置,它只是比较倒霉正好申请内存发现不足了。

直接找重点GC日志类似这样:

 I art     : Starting a blocking GC Alloc
 I art     : Starting a blocking GC Alloc
 I art     : Waiting for a blocking GC Alloc
 I art     : Alloc sticky concurrent mark sweep GC freed 5223(480KB) AllocSpace objects, 5(100KB) LOS objects, 1% free, 189MB/192MB, paused 2.153ms total 35.644ms
 I art     : WaitForGcToComplete blocked for 21.733ms for cause Alloc
 I art     : Starting a blocking GC Alloc

相信网上解释这几句日志的博客已经很多了,就是现在已用内存189M上限是192M,应用下一次申请超过剩余操作限制了就OOM了。

首先确认信息是否符合系统设定

查看系统设置单个进程的内存上限

adb shell
root@125:/ # getprop|grep heapgrowthlimit
[dalvik.vm.heapgrowthlimit]: [192m]

没错就是配置的192M,你的应用消耗的太多了,肯定是有内存泄露了的。

那么怎么查找问题呢,通过这个日志直接分析不到问题,只能根据日志看出应用的大致操作流程,不过Monkey几万次估计所有逻辑都走到了,还是对整个应用内容的使用情况进行检查。

Android上应用开发常见的内存泄露的类型可以看看大神写的http://blog.csdn.net/u010687392/article/details/49909477

其实自从Bitmap的内存分配到java层之后这一类很容易搞定了,用弱引用的缓存直接就安了,这个直接全代码搜索一下挨个改就行了;

其他的同样显性的BraodcastReceiver,ContentObserver,File,Cursor,Stream这一类全代码搜索改就行了;

剩下的比较难找的就是Activity的泄露同时也是应用主要的内存泄露问题,这个如果对代码很熟悉也可以对代码中所有的context、handler等搜索处理,但是如果封装层次比较深模块代码量比较大的情况下,会花费较长的时间。

首先可以用adb shell dumpsys meminfo package name看看当前进程的内存使用情况

Applications Memory Usage (kB):
Uptime: 774504 Realtime: 774504


** MEMINFO in pid 5592 [package namer] **
                   Pss  Private  Private  Swapped     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------
  Native Heap     9076     8980        0        0    12776    12483      292
  Dalvik Heap    12276    12200        0        0    14499    13738      761
 Dalvik Other      956      956        0        0
        Stack      328      328        0        0
      Gfx dev     3926     3056        0        0
    Other dev        5        0        4        0
     .so mmap     2320      464       60        0
    .apk mmap      136        0       16        0
    .ttf mmap      346        0      204        0
    .dex mmap     2524        0     2520        0
    .oat mmap     4493        0     1756        0
    .art mmap     2465     1196      628        0
   Other mmap       51        4       40        0
   EGL mtrack    28224    28224        0        0
      Unknown      233      232        0        0
        TOTAL    67359    55640     5228        0    27275    26221     1053


 Objects
               Views:      271         ViewRootImpl:        1
         AppContexts:        7           Activities:        1
              Assets:        3        AssetManagers:        3
       Local Binders:       22        Proxy Binders:       32
       Parcel memory:        8         Parcel count:       34
    Death Recipients:        0      OpenSSL Sockets:        0


 SQL
         MEMORY_USED:     1206
  PAGECACHE_OVERFLOW:      946          MALLOC_SIZE:       62


 DATABASES
      pgsz     dbsz   Lookaside(b)          cache  Dbname
         1     1673            281     834/125/25  /data/data/package name/databases/sqlit.db

如果此时这里的Activities与目前应用的运行状态不一致,比如目前退出了应该为零但是这里还有数值,很显然就是有Context泄露了(其实这里还能看到很多其他内存数据),这时候就可以使用MAT工具进行分析。

先生成一个hprof文件,然后用MAT工具打开,直接在总览界面选择

Histogram: Lists number of instances per class

这时会将所有当前对象及其数量Heap大小一起列出来,非常的多,可以直接输入Activity或者其他关键字进行搜索。

如果其中的Objects数目与当前应用运行情况不一致就是有问题的地方。

在此条记录上右键选择Merge Shortest Paths to GC Roots --> with all references就可以列出当前对象到GC Root之间的最短引用链,找到并打断这个引用关系就是我们工作

这个栗子由于项目关系就不能贴上具体代码和截图了,最终是在一个单例中发现了一个HashMap引用了一系列的Context在里面。

好了,分析完毕,为了最小的改动,直接将HashMap修改成WeakHashMap,这个东西是在检测到某个key值没有被引用的情况下会回收掉entry,最后检查一下它的get方法如果为空会不会有问题,看一遍没有问题。


问题解决,虽然看起来分析的很愉快,但是在项目的关键时期大半夜看这个还是很伤的,开发者还是需要在写代码的时候就注意内存泄露的问题,尤其是Context的使用,没事多用Application的Context,实在不行也要注意及时主动置空和弱引用。


阅读更多
个人分类: Android应用实践
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭