分析内存泄漏的工具与方法

1.adb shell && Memory Usage

以通过命令 adb shell dumpsys meminfo [package name] 来将指定 package name 的内存信息打印出来,这种模式可以非常直观地看到 Activity 未释放导致的内存泄漏:

或者也可以通过 Android studio 的 Memory Usage 功能进行查看,最后的结果是一样的:

2.利用finalise方法

上面介绍finalise方法时有说过:当GC准备回收一个Java Object(所有Java对象都是Object的子类)的时候,GC会调用这个Object的finalize方法。也就意味着如果某个对象比如activity泄漏的话,那么在退出这个activity时,GC不会调用这个activity的finalize方法,我们可以利用这一点,重写activity的finalise方法,在里面打印一段日志,如下所示:

我们balabala打开很多activity,再返回到主页,通过IDE手动触发几次GC操作,如果这时某个activity没有打印finalise方法里的日志时,说明这个activity就发生内存泄漏了。怎么样?是不是很简单粗暴!

需要注意一点的是,由于重写了finalize方法的对象要第二次GC才能会真正被GC回收,这也是一种泄漏,所以检查完内存泄漏后,别忘了删除重写的finalise方法。

 

3.Allocation Tracker

Android studio 还自带一个 Allocation Tracker 工具,功能和 DDMS 中的基本差不多,这个工具可以监控一段时间之内的内存分配:

在内存图中点击途中标红的部分,启动追踪,再次点击就是停止追踪,随后自动生成一个 .alloc 文件,这个文件就记录了这次追踪到的所有数据,然后会在右上角打开一个数据面板:

 

4.Android Memory Monitor

Memory Monitor 是 Android Studio 自带的一个监控内存使用状态的工具,入口如下所示:

// 待补充图片

在 Android Monitor 点开之后 logcat 的右侧就是 Monitor 工具,其中可以检测内存、CPU、网络等内容,我们这里只用到了 Memory Monitor 功能,点击红色箭头所指的区域,就会 dump 此时此刻的 Memory 信息,并且生成一个 .hprof 文件,dump 完成之后会自动打开这个文件的显示界面,如果没有打开,可以通过点击最左侧的 Capture 界面或者 Tool Window 里面的 Capture 进入 dump 的 .hprof 文件列表:

首先左上角的下拉框,可以选择 App Heap、Image Heap 和 Zygote Heap,对应的就是上篇博客讲到的 Allocation Space,Image Space 和 Zygote Space,我们这里选择 Allocation Space,然后第二个选择 PackageTreeView 这一项,展开之后就能看见一个树形结构了,然后继续展开我们应用包名的对应对象,就可以很清晰的看到有多少个 Activity 对象了,上面那两栏展示的信息按照从左到右的顺序,定义如下所示:

Column Description
Class Name 占有这块内存的类名
Total Count 未被处理的数量
Heap Count 在上面选择的指定 heap 中的数量
Sizeof 这个对象的大小,如果在变化中,就显示 0
Shallow Size 在当前这个 heap 中的所有该对象的总数
Retained Size 这个类的所有对象占有的总内存大小
Instance 这个类的指定对象
Reference Tree 指向这个选中对象的引用,还有指向这个引用的引用
Depth 从 GC Root 到该对象的引用链路的最短步数
Shallow Size 这个引用的大小
Dominating Size 这个引用占有的内存大小

然后可以点击展开右侧的 Analyzer Tasks 项,勾选上需要检测的任务,然后系统就会给你分析出结果:

// 待补充图片

从分析的结果可以看到泄漏的 Activity 有两个,非常直观,然后点开其中一个,观察下面的 ReferenceTree 选项:

// 待补充图片

可以看到 Thread 对象持有了 SecondActivity 对象的引用,也就是 GC Root 持有了该 Activity 的引用,导致这个 Activity 无法回收,问题的根源我们就发现了,接下来去处理它就好了。

 

5.MAT

MAT(Memory Analyzer Tools)是一个 Eclipse 插件,它是一个快速、功能丰富的 Java heap 分析工具,它可以帮助我们查找内存泄漏和减少内存消耗,MAT 插件的下载地址:Eclipse Memory Analyzer Open Source Project,上面通过 Android studio 生成的 .hprof 文件因为格式稍有不同,所以需要经过一个简单的转换,然后就可以通过 MAT 去打开了:   通过 MAT 去打开转换之后的这个文件:

用的最多的就是 Histogram 功能,点击 Actions 下的 Histogram 项就可以得到 Histogram 结果:

我们可以在左上角写入一个正则表达式,然后就可以对所有的 Class Name 进行筛选了,很方便,顶栏展示的信息 “Objects” 代表该类名对象的数量,剩下的 “Shallow Heap” 和 “Retained Heap” 则和 Android Memory Monitor 类似。咱们接着点击 SecondActivity,然后右键:

在弹出来的菜单中选择 List objects->with incoming references 将该类的实例全部列出来:

通过这个列表我们可以看到 SecondActivity@0x12faa900 这个对象被一个 this$00x12c65140 的匿名内部类对象持有,然后展开这一项,发现这个对象是一个 handler 对象:

快速定位找到这个对象没有被释放的原因,可以右键 Path to GC Roots->exclude all phantom/weak/soft etc. references 来显示出这个对象到 GC Root 的引用链,因为强引用才会导致对象无法释放,所以这里我们要排除其他三种引用:

这么处理之后的结果就很明显了:

一个非常明显的强引用持有链,GC Root 我们前面的博客中说到包含了线程,所以这里的 Thread 对象 GC Root 持有了 SecondActivity 的引用,导致该 Activity 无法被释放。

MAT 还有一个功能就是能够对比两个 .hprof 文件,将两个文件都添加到 Compare Basket 里面:    添加进去之后点击右上角的 ! 按钮,然后就会生成两个文件的对比:

同样适用正则表达式将需要的类筛选出来:

结果也很明显,退出 Activity 之后该 Activity 对象未被回收,仍然在内存中,或者可以调整对比选项让对比结果更加明显:

也可以对比两个对象集合,方法与此类似,都是将两个 Dump 结果中的对象集合添加到 Compare Basket 中去对比,找出差异后用 Histogram 查询的方法找出 GC Root,定位到具体的某个对象上。

 

6.LeakCanary

LeakCanary可能是上面几个工具中最好用的了,用法如下:

在build.gradle文件中添加如下依赖:

 dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
   testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
 }

然后在application中添加如下代码:

public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    }
    LeakCanary.install(this);
    // Normal app init code...
  }
}

OK,配置完成!噼里啪啦多点开几个页面,看看LeakCanary是否已经全都暴露出来了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章