MAT在内存分析中的简单使用

在Android开发过程中,经常会遇到各种内存泄漏和内存溢出的问题,所谓的内存泄漏是指部分已经不再使用的变量还继续占用内存得不到及时释放,而内存溢出则是指Android虚拟机会给每个应用(对应一个进程)可分配的内存是有限的,当该应用占用的内存达到可分配的最大内存时,应用继续申请内存,这是就会出现内存溢出。内存溢出多是内存泄漏导致的,内存泄漏和内存溢出都会降低应用运行效率,导致应用卡顿,所以在日常开发中应该竟可能避免内存泄漏和内存溢出。

分析内存泄漏的方法很多,比如可以直接检查代码查看是否有在Activity的非静态内存类或匿名内存类执行耗时操作,是否在使用非静态的Handler中执行耗时操作,是否在Activity中注册BroadCast Receiver然后没有及时释放,是否有强引用图片没有及时释放,是否申请了IO变量而没有手动释放等,另外也可以通过LeakCanary工具集成到应用中来debug出内存泄漏原因。然而这些都不是本文想说的,本文就是想简单的介绍下就Android Studio结合MAT(memery analizer tool)在分析内存泄漏时的简单应用。

这里在介绍MAT分析内存之前,先说说对应每个应用的三个内存参数

maxMemory:Andriod虚拟机所能够分配给应用的最大内存;
totalMemory:当前内存已经获得的内存值;
freeMemory:应用当前已使用的内存值;

当用用的当前已经占用的内存,达到totalMemory的一定比例后,虚拟机就给应用再分配一定的内存,即应用的totalMemery会变大,totalMemery的最大值就是maxMemery
这三个参数可通过如下方式获取

Runtime.getRuntime().maxMemory();
Runtime.getRuntime().totalMemory();
Runtime.getRuntime().freeMemory();

前面说好了些基本的概念,下面就来说说Android Studio如何检测内存使用,并且获取可以用来分析内存的hprof文件。

1、Android Studio的MEMORY工具

这里通过写一个LeakActivity,然后通过Android Studio的MEMORY工具来检测当前的内存使用情况,LeakActivity代码如下

public class LeakActivity extends Activity {
    private ArrayList<String> stringList = new ArrayList<String>();
    private ArrayList<Bitmap> bitmapList = new ArrayList<Bitmap>();
    private String str = "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" +
            "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" +
            "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" +
            "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" +
            "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" +
            "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" +
            "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" +
            "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" +
            "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leak);
        initBitmapData();
        //initStringData();
    }

    private void initStringData(){
        for(int i = 0;i < 5000;i++){
            stringList.add(str);
        }
    }

    private void initBitmapData(){
        for(int i = 0;i < 3000;i++){
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inSampleSize = 1;
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher,options);
            bitmapList.add(bitmap);
        }
    }

    public void onHandler(View iew){
        Toast.makeText(LeakActivity.this,"onHandler Clicked",Toast.LENGTH_SHORT).show();
        for(int i = 0;i < 20;i++){
            Bundle bundle = new Bundle();
            bundle.putString("name","yoryky");
            bundle.putInt("age",15);
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher);
            bundle.putParcelable("image",bitmap);
            Message message = new Message();
            message.what = 1;
            message.obj = bundle;
            handler.sendMessageDelayed(message,1000*10);
        }
    }

    private Handler handler= new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };
}

首先运行应用,然后由Android Profiler-> MEMORY打开内存工具,然后就可以看到内存使用情况了,操作应用,由MainAcivity跳转到这里的LeakActivity,点击触发onHandler方法,然后促使handler开发发送延时消息,然后我们再返回MainActivity,我们看这时的MEMORY的内存情况
这里写图片描述
点击图片中的Dump Java Heap可获取当前的Java Heap使用情况,也可以导出leak3.hprof文件,供之后MAT来分析。
这里从MEMORY分析工具,我们就可以清晰的看到在页面已经返回MainActivity后,LeakActivity本来应该销毁了,但是这是获取到的页面居然有LeakActivity,同时还有两个MessageQueue,以及很多Message对象(有一个MessageQueue,以及一些Message是正常的,因为主线程本身持有一个MessageQueue来处理消息),这是我们就可以初步推断出是LeakActivity页面产生了内存泄漏了。同时,这里可以看到Bitmap占用了很大的的空间,这里占用的内存空间有两个衡量值,一个是Shallow Size,另一个是Retained Size这里也说说吧

Shallow Size: 指对象本身所占用的内存空间大小,不包含其所引用的对象所占的内存空间;
Retained Size: 指对象本身以及其所引用的对象所占内存空间大小;

同时,我们这里看到Bitmap对象,所占用的内存空间是Native类型的内存空间(关于内存空间类型这里就不讨论了),这个需要注意。Android在3.X之前Bitmap是放在Native Heap中的,之后版本放到Java Heap中,8.X之后版本又放到Native Heap中,放在Native Heap的值实际上是不算在应用的maxMemory中的,同事在Native Heap中的Bitmap,下面要讲的MAT工具中的也检测不到的,也就是说Bitmap的内存泄漏,单靠MAT的内存分析,是分析不到,这个要结合这里的Android Studio的MEMORY结合使用来判断。

这一小节,我们知道了Android Studio中MEMORY工具的使用,并且获取了一个leak3.hprof的内存数据,实际上这个文件可以直接拉到Android Studio中,进行分析,童鞋们可以自己试试。

2、MAT的简单使用

上一小结获取到了一个leak3.hprof文件,是否可以直接用MAT打开使用呢?答案是否定的,MEMEORY工具获取到的hprof文件和MAT能够分析的hprof格式不一样,需要通过sdk的platform-tools中的hprof-conv工具,作一次转换
这里写图片描述

通过转换后,这里得到MAT能够识别的leak_conv3.hprof文件,然后通过MAT打开该文件,通过Leak Suspects分析出下面的饼状图
这里写图片描述

这个饼状图给出了3个可能的内存泄漏点,注意这个只是指出了存在内存泄漏的可能,而不是表示一定出现了内存泄漏,这里打开Problem Suspect1的Details看看
这里写图片描述

这里看到有String对象出现内存泄漏,应该对应LeakActivity页面中str变量(LeakActivity没有释放,所以全局变量str也没有释放)。

这个是MAT的Leak Suspects工具的使用,再看看Histogram工具的使用
这里写图片描述

这里我们看看android.graphics.Bitmap对象的详情,通过右键 -> Merge Shortest Paths to GC Roots -> exclude all phantom/weak/soft etc. refrences 然后可以如下图所示看到,原来这个Bitmap对象,有一部分是LeakActivity中的3000ge Bitmap对象没有得到释放,同时这里也应该注意到这里的Bitmap的Retained Heap值居然只有48也就是,MAT统计出的Bitmap所占用的内存空间,没有算其实际占用的内存空间大小,只是算了其引用所占的大小,这个一定要注意。
这里写图片描述

这里还可以看看Histogram的另一中使用方式,就是比较一个页面不同状态时的内存差别,我们在打开LeakActivity时,不触犯onLeak方法,然后返回MainActiviy重新抓取一个leak1.hprof,同样的转为MAT能识别的leak_conv1.hprof文件,然后用MAT打开
这里写图片描述

点击上面的柱形图标,然后点击页面下部的histogram,右键Add to Compare Basket,同样的方法对leak_conv3.hprof也用以一次,这样Compare Basket就用两个文件了
这里写图片描述

点击红色感叹号就可以完成对比了,从对比图中,可以发现leak_conv3.hprof比leak_con1.hprof的Bitmap对象要多3000多个,这个也比价容易发现内存泄漏点
这里写图片描述

到这里就比较简单的介绍了下MAT在内存分析中的使用,主要介绍了其Leak Suspects以及Histogram这两个方法的使用。

3、使用命令获取hprof文件

可能有些童鞋要说,我如果是用Eclispe呢,我如果不想安装Android Studio呢,有没有其她办法获取hprof文件呢?这里提供一个adb命令来获取hprof文件的方法,这里以这里的com.example.test包名的模块为例来说明,注意在具体使用中替换为自己的包名
首先,adb shell进入shell命令,获取模块进程id

ps -A | grep com.example.test

这里写图片描述

进程id为18337,然后通过如下命令在手机对应目录下生成hprof文件

am dumpheap 18337 /data/local/tmp/leak.hprof

最后,退出shell命令后,通过如下命令可将对应hproft文件拉去到电脑中来

adb pull /daa/local/tmp/leak.hprof

到此为止,本文浅薄的对MAT在内存分析中的使用就总结完毕,望纰漏指出童鞋们能够不吝指出。

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