内存优化(四):Mat大对象与泄露检测

使用android shell命令查看内存使用情况

使用adb shell dumpsys meminfo pkgname或者直接使用AndroidStudio里面的memory usage功能然后就会出现如下信息:

Applications Memory Usage (kB):
Uptime: 14237237 Realtime: 23790474

** MEMINFO in pid 8071 [com.xtc.watch] **
                   Pss  Private  Private  Swapped     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------
  Native Heap        0        0        0        0    21924     8558     6405
  Dalvik Heap   122472   122372        0    15672   143308    65400    77908
 Dalvik Other    10361    10076      164      224
        Stack      440      440        0        8
    Other dev        4        0        4        0
     .so mmap     6441     3452     2636     2048
    .apk mmap      611        0      340        0
    .ttf mmap      538        0      504        0
    .dex mmap     8407     1640     2940       40
   Other mmap       80        4        0        0
      Unknown    10940    10936        0      148
        TOTAL   160294   148920     6588    18140   165232    73958    84313

 Objects
               Views:      288         ViewRootImpl:        2
         AppContexts:       11           Activities:        2
              Assets:        5        AssetManagers:        5
       Local Binders:       30        Proxy Binders:       38
    Death Recipients:        3
     OpenSSL Sockets:        1

 SQL
         MEMORY_USED:      138
  PAGECACHE_OVERFLOW:       24          MALLOC_SIZE:       62

 DATABASES
      pgsz     dbsz   Lookaside(b)          cache  Dbname
         4       20            306       25/47/12  /data/data/com.xtc.watch/databases/upload.db

  • Native Heap是native层的内存堆栈,Dalvik Heap是java层的内存堆栈,如果这二者加起来的内存占用超过了应用最大内存限制就会报OOM异常,剩下的.so mmap是 C 库代码占用的内存,.jar mmap是Java 文件代码占用的内存 ,.apk mmap是apk代码占用的内存,.dex mmap是Dex 文件代码占用的内存
  • Objects中的Activities表示当前内存中的activity对象的个数,启动一个activity就会生成一个activity对象,当退出activity的时候,activity对象就会被释放,所以反复的进出一个activity界面然后查看Activities的个数有没有保持不变,如果增加了,那么就说明这个activity对象没有被释放,也就是说可能存在内存泄漏,但是具体哪里泄漏了并不知道

DDMS查看内存使用情况

eclipse中有一个ddms工具,可以查看线程信息(Threads),内存使用情况(VM Heap),内存分配跟踪(Allocation Tracker),CUP使用情况(Sysinfo CUP load),内存使用饼状图(Sysinfo Memory usage),这里我们暂时用到VM Heap,选择要查看的app进程,点击左上角的show heap updates,选择VM Heap并点击Cause GC按钮,然后就出现下图:

image.png

观察data object的Total Size选项,这个是app的创建的java对象做占用的内存大小,Count是总内存的对象的个数,反复的进出一个activity,看data object的Total Size有没有明显的增加,正常情况下进入一个activity的时候会明显增加,退出一个activity会有明显的回落,总体是维持在一个比较稳定的水平如果反复进出activity,Total Size不断上升,那么可能就存在内存泄漏了,需要具体排查

MAT分析内存泄漏,用AndroidStudio的Monitors的Memory

  • 多点击几下Initiate GC来回收一下可被释放的java对象,因为java的GC是定期有条件执行的,当内存中只存在很少的无用对象,这时候可能并不会触发GC,所以手动触发GC来保证开始检测内存的时候内存都是最干净的

  • 点击Dump Java Heap,然后过一会儿就会出现一份数据分析文件,这时候的这份数据文件是刚开始的程序对象内存占用情况,接下去就针对一个activity反复操作进出等等各种反复操作,觉得差不多了,这时候就再次疯狂点击Initiate GC回收一下可是放的对象,点击Dump Java Heap,这时候生成的数据分析文件就是经过你疯狂操作后的内存占用情况了

image.png
生成的上述两个文件右键,点击Export to Standar .hprof导出到一个自己指定的目录文件夹
  • 去官网上面下载MAT来打开这两个文件开始内存分析
image.png
上图中Problem Suspect部分是代表可能存在内存泄漏的地方,Remainder表示正常的部分,再继续往下看
image.png
image.png
上面就是对可能存在内存泄漏部分的代码的一个详细的信息,可以看到有些byte数组占用了大量的内存,keywords也是byte[],第二张图的DexCache可能占用的较多的内存,再点击
image.png
红色部分就会出现一些对象的信息列表:
image.png
可以输入正则表达式来筛选你想要的类,包名下所有的类,Objects是对象的个数,Shallow Heap是当前对象所占用的内存大小,不包括对象内包含的对象的大小,Retained Heap表示当前对象包括对象内的子对象一共占用的内存大小,所以Retained Heap会比Shallow Heap大得多,对于一些我们已知的对象在内存不泄露的情况下,该对象的个数是确定的,所以可以通过分析Objects的个数来确定对象是否存在内存泄漏,例如同一个Activity对象在反复进出该Activity5次之后Objects的值为5,那就有问题了,说明同一个Activity创建了5个对象,正常情况下应该是退出Activity后,对象会被回收的,所以Objects的值应该是0才对,而有些对象的Objects不为0并不代表一定存在内存泄漏,例如ConnectionService是一个常驻的Service,那么它是不会被GC的,而ConnectionService里面的对象可不会被回收,所以这些对象的Objects值不为0其实就是正常的了,至于Shallow Heap和Retained Heap,我觉得可以用来分析一些对象的内存占用,Shallow Heap一般情况下不会很大,当你发现Retained Heap非常大的时候,那就说明该对象里面的对象可能占用了大量的内存,可能存在问题;在用Objects找到可能存在内存泄漏的对象后,右键List Objects,然后有两个选项:with outgoing references(表示的是当前对象,引用了外部引用)和with incoming references(表示的是当前查看的对象,被外部引用),一般当前对象泄漏了就是对象还被外部对象持有引用,无法被释放,所以我们选择查看with incoming references
image.png
,点击with incoming references后
image.png
可以看到TaskExecutor对象被外部的两个对象所引用到了,并且可以看到引用的路径,右键TaskExecutor,选择Path to GC Roots或者或者Merge Shortest Paths to GC Roots选项,再选择exclude all phantom/weak/soft .ect references排除所有的弱引用,软引用对象寻找看有没有存在GC Roots,如果没有那就说明这个对象不存在内存泄漏,最终是会被GC回收,如果存在GC Roots
image.png
在继续查看GC Roots的对象是什么,其实就是被哪一个对象所引用了,上图可以查看到被外部的sendTaskExecutor对象所引用了而sendTaskExecutor是在ConnectionService这个常驻Service中的,所以理论上是不应该被回收的,所以这里不算是内存泄漏,假如sendTaskExecutor是一个Activity里面的字段,而此时Activity已经退出了,那么这时候就属于内存泄漏了,因为Activity退出后,Activity资源包括里面的对象应该是被回收掉的,那就找到对应的代码去具体分析可能造成内存泄漏的问题所在了,这里明确一点,存在GC Roots的不一定就一定存在内存泄漏,GC是不会回收GC Root或者被GC Root所引用的对象的,java对象内存泄漏其实就是对象在不用的时候仍然被其他对象持有引用导致GC无法回收

比较反复操作前的内存信息和反复操作后的内存信息分析前后有哪些不同

image.png

,点击红色部分后会出现下图

image.png

可以看到对比后的Objects和Shallow Heap信息,看Objects表示前后两种内存信息对于同一个对象是否有个数上的增加,因为如果对象能被正常回收,那么开始操作前是0,操作后经过GC应该也是0,如果个数增加了,那就表示这个对象可能存在内存泄漏了,拖动到底部可看到总的比较情况

image.png

上图可知,操作前的app的Objects总数比操作后的Objects少了1个,同时可以看到是HttpDns这个对象的问题,于是再切换到操作后的histogram去按照上述步骤查找GC Roots,再具体分析内存泄漏的问题

  • MAT当中还有一个dominator tree视图,具体就不复述了,可以参考这篇大神文章MAT内存分析

你可能感兴趣的:(内存优化(四):Mat大对象与泄露检测)