MAT检测模拟内存泄露

MAT检测模拟内存泄露

名词解释:
MAT(Memory Analyze Tool):顾名思义,MAT就是内存分析工具。是一个快速且功能丰富的堆转储分析软件,它可以帮助你找到内存泄露和分析高内存消耗问题。

内存溢出(OOM):即Out of Memory。内存溢出是指APP向系统申请超过最大阀值的内存请求,系统不会再分配多余的空间,就会造成OOM error。

内存泄露(memory leak):当一个对象已经不需要再使用了,本该被回收时,而另外一个正在使用的对象持有它的引用从而导致它不能被回收,这就导致本该被回收的对象不能被回收而停留在堆内存中。

GC(Garbage Collections): 字面意思是垃圾回收器,释放垃圾占用的空间。

首先我们先来了解为什么使用这个工具:
Android系统为每个应用程序分配的内存有限,当一个应用中产生的内存泄漏(Memory Leak)比较多时,就难免会导致应用所需要的内存超过这个系统分配的内存限额,这就造成了内存溢出(Out of Memory)而导致应用Crash。

事前准备:
安装MAT:
MAT是Eclipse的一个插件,我们使用Android Studio配合MAT定位内存泄露问题之前,要先下载MAT工具。
下载地址:https://www.eclipse.org/mat/downloads.php
在ubuntu系统,下载解压后即可使用
安装hprov-conv:
Android Studio中的Android Profiler生成的hprof文件不能够被MAT直接打开,需要通过以下命令进行转换
hprof-conv -z <源文件> <产出文件>
所以需要提前安装好,安装命令如下:

$sudo apt-get install hprof-conv

模拟内存泄露:
ActivityA作为程序的入口,通过ActivityA打开ActivityB,ActivityB在传入自身的context获取AppManager单例,然后点击Button创建多个ImageView对象并保存到List中;然后退出ActivityB,在profiler手动GC,此时AppManager持有对ActivityB的引用,导致ActivityB不能够被正常回收。
ActivityA只有一个按钮跳转ActivityB,这里就不把ActivityA的代码贴出来了。
ActivityB代码如下:

public class ActivityB extends Activity {
    private List list;
    @Override 
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_b);
        AppManager.getInstance(this);
        list = new ArrayList();
        Button button = findViewById(R.id.ActivityB_button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override 
            public void onClick(View v) {
                for (int i = 0; i < 10000; i++) {
                    ImageView imageView = new ImageView(ActivityB.this);
                    list.add(imageView);
                }
            }
        });
    }
}

AppManager的代码如下

public class AppManager {
    private static AppManager instance;
    private Context context;

    private AppManager(Context context) {
        this.context = context;
    }

    public static AppManager getInstance(Context context) {
        if (instance == null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

通过Android Studio中的Profiler(关于Profiler的操作可以自行百度)获取hprof文件:
打开Profiler,选择分析我们想要的进程,然后打开Memory分析界面。下面开始模拟内存泄露过程
1.通过ActivityA打开ActivityB,随后多次点击ActivityB中的Button,可以看到内存的使用快速地增长。
在这里插入图片描述
2.按强制gc,内存没变。是因为所添加的imageView都被list所引用着
在这里插入图片描述
3.此时,我们退出ActivityB,再次多次触发Froce GC,我们理想的效果是回收内存,内存使用状况与打开ActivityB之前接近。不过实际情况是内存的占用依然没下去,此时就产生了严重的内存泄露(Momery Leak)。
在这里插入图片描述
4.点击Dump Java heap,然后Export生成的Heap Dump到计算机中(建议新建一个文件夹,MAT在打开hprof文件时,会产生很多文件)。
5.最后在终端输入下面的命令:
hprof-conv -z memory-20190531T154103.hprof memory-20190531T154103_mat.hprof
//hprof-conv -z <源文件> <产出文件>
获取到能被MAT工具所打开的hprof文件。

MAT工具的使用:
打开MAT,导入hprof文件。
成功导入后会有一个入门向导,让我们选择打开哪些报告,我们后续都可以在面板中打开,所以点击cancel就好。
Overview窗口:
打开之后的界面如下图;只有一个Overview窗口展示应用的内存的使用情况。
在这里插入图片描述
Histogram窗口:
我们点击如下按钮,打开Histogram窗口。Histogram可以展示某个特定类的对象个数和每个对象使用的内存。

界面如下:
在这里插入图片描述
在这里插入图片描述
我们点击Group by class根据类分组

表格中有4个列名Class Name,Objects,Shallow Heap和Retained Heap。
Class Name:类名
Objects:对应的对象个数
Shallow Heap:一个对象消费的内存数量
Retained Heap:回收所有对象的内存数量
我们可以看到有40000个ImageView的对象,这就是我们刚才内存泄露,无法被正常回收的对象,占用了大部分的内存资源。
如果我们想要看到哪里调用了这个类,右键点击想要查看的类–>Merge Shortest Path to GC Roots–>exclude all phantom/weak/soft etc.referencer
可以清晰地看到引用关系,AppManager类的实例的成员变量context含有对ActivityB对象的引用,导致ActivityB对象中的list无法被释放。
在这里插入图片描述

Dominator tree窗口:
我们点击如下按钮,打开Dominator tree窗口。由你系统中的复杂的Object graph(对象引用图)生成的树状图。Dominator tree可以让你分别出最大内存图表。如果所有指向对象Y的路径都经过对象X,则认为对象X支配对象Y。通过查看本例的Dominator tree,我们开始明白到底是哪些内存块发生了泄露。

界面如下:
在这里插入图片描述

模拟内存泄露的解决办法:
我们先来分析一下内存泄露的原因:AppManager的一个静态实例持有对ActivityB对象的context的引用,导致退出ActivityB时,ActivityB对象和对象中的成员变量也无法被正常回收。
这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:
1)、传入的是Application的Context:这将没有任何问题,因为单例的生命周期和Application的一样长;
2)、传入的是Activity的Context:当这个Context所对应的Activity退出时,由于该Context和Activity的生命周期一样长(Activity间接继承于Context),所以当前Activity退出时它的内存并不会被回收,因为单例对象持有该Activity的引用。
所以正确的单例应该修改为下面这种方式:

public class AppManager {
    private static AppManager instance;
    private Context context;

    private AppManager(Context context) {
        this.context = context.getApplicationContext();
    }

    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

修改后,我们重复上面获取hprof文件的步骤:

1.通过ActivityA打开ActivityB,随后多次点击ActivityB中的Button,可以看到内存的使用快速地增长。
2.多次触发Force GC,内存的占用一直下不去。因为ActivityB持有List对象的引用,List对象不会被回收。
3.此时,我们退出ActivityB,再次多次触发Froce GC。ActivityB中占用的内存正常被回收。
在这里插入图片描述
延伸:
常见的的内存泄露:
1.单例造成的内存泄漏
2.非静态内部类创建静态实例造成的内存泄漏
3.Handler造成的内存泄漏
4…

参考
关于Android中内存泄露与如何有效避免OOM总结:https://blog.csdn.net/gjnm820/article/details/51579080
Memory Analyzer的使用技巧:https://www.jianshu.com/p/759e02c1feee

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值