性能优化之内存泄漏分析

内存泄漏分析主要可以使用 Android Studio 的 Memory Monitor 工具以及 MAT来分析。
如下是一段用来测试的代码:

public class LeakActivity extends Activity {

    InnerClass mInnerClass;
    //static InnerClass mInnerClass;
    List<Button> mBtns = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leak);

        if(mInnerClass == null) {
            mInnerClass = new InnerClass();
        }

        for(int i=0; i<1000; i++) {
            Button btn = new Button(this);
            mBtns.add(btn);
        }
    }

    class InnerClass {
        void doSomeThing() {
            System.out.println("doSomeThing");
        }
    }
}

一、Memory Monitor

打开Android Studio 的 Android Monitor,选择 Memory Monitor 可以实时查看到内存的动态分配情况。

  • 正常没有内存泄漏的情况

我们从首页打开 LeakActivity 两次,然后再按返回键返回到首页。然后再进行手动 GC 两次,结果如下:
这里写图片描述
从图中可以看到经过两次GC后,内存回到最初进首页时的2M了。说明没问题,LeakActivity实例被回收了。

  • 内存泄漏情况

给成员变量 mInnerClass 加上 static 关键字,然后重新运行程序并做同样的操作,结果如下:
这里写图片描述
可以看到进行两次手动 GC 后,内存还是没完全下去,有大概 8M 没回收掉。这时就出现了内存泄漏,有一个 LeakActivity 实例被回收不了。

以上图片只能大概看出内存的分配大小情况,对于想知道堆内存中存储了什么。还得用Heap Viewer。

关于GC过程,可以查看这篇文章:Java虚拟机之垃圾收集过程

Heap Viewer的使用
出现了内存泄漏的情况,我们来看下 Java Heap 情况。点击如下红色框内的 Dump Java Heap 按钮,就会生成 .hprof 文件。
这里写图片描述
生成 .hprof 文件后,Android Studio会自动打开如下 Heap Viewer 分析界面。
这里写图片描述
点击右上角区域中红色框起来的 Perform Analysis 按钮会进入HPROF Analyzer的hprof的分析界面:
这里写图片描述
可以看到右边框起来的Analyzer Tasks 区域中给出分析结果了,LeakActivity 发生泄漏了。
从下面的区域中可以看出 LeakActivity 被 this0 引用了,而 this0 是表示内部类的意思,可以看到这个内部类就是 InnerClass 。

二、MAT

MAT 比 Menmery Monitor 更强大。
MAT工具全称为Memory Analyzer Tool,下载地址:http://eclipse.org/mat/downloads.php。MAT是一款详细分析Java堆内存的工具,该工具非常强大,为了使用该工具,我们需要hprof文件。

1、获取hprof文件

从Android Studio 获取
当我们点击Memory Monitor 的 Dump Java Heap 按钮后,AS自动为我们生成了hprof 文件,点击左侧的 Captures 图标会列出 hprof 文件列表:
这里写图片描述
这时需要按图示转换成 MAT 可以识别的标准 hprof 文件,然后就可以使用MAT打开分析了。

使用DDMS获取
从AS 的Tools菜单中打开 Android Device Monitor。
这里写图片描述
然后就可以Android Device Monitor的界面了。
和上面做同样的操作,从首页打开 LeakActivity 两次,然后再按返回键返回到首页。然后点击下图画圈的按钮,进行手动 GC 两次。
这里写图片描述
然后再点击旁边的按钮获取 hprof 文件。
这个是Android的hprof文件,需要转换成 Java 的hprof文件,MAT才能打开。
转换工具需要使用 sdk里面的hprof-conv.exe,路径:sdk 的 platform-tools 目录下。
转换命令示例:
这里写图片描述
新生成的 output.hprof 就可以使用 MAT 打开分析了。

2、使用MAT分析

使用 MAT 打开 hprof 文件后,这时MAT就会生成报告,这个报告分为两个标签页,一个是Overview,一个是Leak Suspects(内存泄漏怀疑)。如下图:
这里写图片描述
Leak Suspects中会给出了MAT认为可能出现内存泄漏问题的地方,上图共给出了3个内存泄漏猜想,通过点击每个内存泄漏猜想的Details可以看到更深入的分析清理情况。如果内存泄漏不是特别的明显,通过Leak Suspects是很难发现内存泄漏的位置。

打开Overview标签页,首先看到的是一个饼状图,它主要用来显示内存的消耗,饼状图的彩色区域代表被分配的内存,灰色区域的则是空闲内存,点击每个彩色区域可以看到这块区域的详细信息,如下图所示:
这里写图片描述
再往下看,Actions一栏的下面列出了MAT提供的四种Action,其中分析内存泄漏最常用的就是 Histogram 和 Dominator Tree。我们点击Actions中给出的链接进行查看分析。

  • 2.2.1 Dominator Tree

Dorminator Tree意味支配树,从名称就可以看出Dorminator Tree更善于去分析对象的引用关系。
这里写图片描述
图中可以看出Dorminator Tree有三列数据。
Shallow Heap:对象自身占用的内存大小,不包括它引用的对象。如果是数组类型的对象,它的大小是数组元素的类型和数组长度决定。如果是非数组类型的对象,它的大小由其成员变量的数量和类型决定。
Retained Heap:一个对象的Retained Set所包含对象所占内存的总大小。换句话说,Retained Heap就是当前对象被GC后,从Heap上总共能释放掉的内存。
Retained Set指的是这个对象本身和他持有引用的对象以及这些引用对象的Retained Set所占内存大小的总和。
通过MAT提供的Dominator Tree,可以很清晰的得到一个对象的直接支配对象,如果直接支配对象中出现了不该有的对象,就说明发生了内存泄漏。

在Dominator Tree的顶部Regex可以输入过滤条件(支持正则表达式),如果是查找Activity内存泄漏,可以在Regex中输入Activity的名称,比如我们这个例子可以输 LeakActivity,结果如下图所示。
这里写图片描述
可以看到还是有一个 LeakActivity 没有被回收掉,基本可以断定发生了内存泄漏,具体内存泄漏的原因,可以查看GC引用链。在LeakActivity 一项单击鼠标右键,选择 Paths to GC Root,如下图所示。
这里写图片描述
选择exclude all phantom/weak/soft etc. references,是因为这个选项排除了虚引用、弱引用和软引用,这些引用一般是可以被回收的。这时MAT就会给出LeakActivity 的GC引用链。
这里写图片描述
引用LeakActivity 的是InnerClass ,this$0的含义就是内部类自动保留的一个指向所在外部类的引用,而这个外部类就是LeakActivity ,这将会导致LeakActivity 无法被GC。

  • 2.2.2 Histogram

Histogram与Dominator Tree不同的是,Dominator Tree是在对象实例的角度上进行分析,注重引用关系分析,而Histogram则在类的角度上进行分析,注重量的分析。
Histogram中的内容如下图所示。
这里写图片描述
在Histogram的顶部Regex同样可以输入过滤条件,这里同样输入LeakActivity ,效果如下图所示。
这里写图片描述
LeakActivity 和InnerClass 实例各为1个,基本上可以断定发生了内存泄漏。具体内存泄漏的原因,同样可以查看GC引用链。在LeakActivity 一项单击鼠标右键,选择 Merge Shortest Paths to GC Root,如下图所示。
这里写图片描述
得到的 LeakActivity 的GC引用链如下:
这里写图片描述
得出的结果和2.2.1节是相同的,引用LeakActivity 的是InnerClass ,这导致了LeakActivity 无法被GC。

三、如何发现内存泄漏

上面分别介绍了使用Android studio和MAT分析内存的方法。Android studio自带的内存分析工具直观方便,但其功能却不如MAT强大,特别是没有有效的搜索、排序等功能。遇到一些棘手的问题,可能还是要借助MAT来分析内存。

上面的例子是我们人为制造了一个内存泄漏,然后有意用工具检测他。但实际开发中,我们如何发现内存泄漏呢?我想可以首先使用studio自带或DDMS中的heap分析工具,观察在反复执行某个操作时(例如打开某个页面、点击某个按钮、加载某个资源等等)时,内存在执行GC后能始终维持在稳定的值附近。如果内存呈线性增长的趋势,那一定是发生了内存泄漏。此时,就要dump出内存镜像,然后使用工具分析了。

在分析内存时,第一是可以使用工具自带的泄漏检查器帮助定位。另外,可以在执行操作(怀疑造成内存泄漏的操作)前后,分别dump出一份内存镜像,然后使用MAT的Compare Basket对比两个文件的内存情况,这样可以帮助定位到是哪个对象发生了泄漏。然后再找到这个对象的GC Roots,这样就可以进一步定位到具体的代码了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值