性能优化工具-MAT的使用

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了性能优化工具-MAT的使用相关的知识,希望对你有一定的参考价值。

参考技术A

最近在写性能优化专题相关文章,依次写了“android电量优化全解析”、“Android性能优化全解析”、“Android渲染优化解析”、“Android计算优化解析”。文章中有提到许多性能优化的工具,但由于文章重点都是如何分析性能相关的论述,对工具的使用介绍大都是简单略过,下面简单介绍下性能优化工具-MAT(Memory Analyzer Tool)使用,介绍的顺序为:

MAT工具全称为Memory Analyzer Tool,一款详细分析Java堆内存的工具,该工具非常强大,为了使用该工具,我们需要hprof文件。但是该文件不能直接被MAT使用,需要进行一步转化,可以使用hprof-conv命令来转化,但是Android Studio可以直接转化,转化方法如下:

1.选择一个hprof文件,点击右键选择Export to standard .hprof选项。

2.填写更改后的文件名和路径。

点击OK按钮后,MAT工具所需的文件就生成了!

下面我们用MAT来打开转换后的doctorq.hprof文件:

1.打开MAT后选择File->Open File选择我们刚才生成的doctorq.hprof文件

2.选择该文件后,MAT会有几秒种的时间解析该文件,有的hprof文件可能过大,会有更长的时间解析,解析后,展现在我们的面前的界面如下:

这是个总览界面,会大体给出一些分析后初步的结论。

该视图会首页总结出当前这个Heap dump占用了多大的内存,其中涉及的类有多少,对象有多少,类加载器,如果有没有回收的对象,会有一个连接,可以直接参看(图中的Unreachable Objects Histogram)。

比如该例子中显示了Heap dump占用了41M的内存,5400个类,96700个对象,6个类加载器, 然后还会有各种分类信息。

会列举出Retained Size值最大的几个值,你可以将鼠标放到饼图中的扇叶上,可以在右侧看出详细信息:

图中灰色区域,并不是我们需要关心的,他是除了大内存对象外的其他对象,我们需要关心的就是图中彩色区域,比如图中2.4M的对象,我们来看看该对象到底是啥

该对象是一个Bitmap对象,你如果想知道该对象到底是什么图片,可以使用图片工具gimp工具浏览该对象。

histogram视图主要是查看某个类的实例个数,比如我们在检查内存泄漏时候,要判断是否频繁创建了对象,就可以来看对象的个数来看。也可以通过排序看出占用内存大的对象:

默认是类名形式展示,你也可以选择不同的显示方式,有以下四种方式:

下面来演示一下:

该视图会以占用总内存的百分比来列举所有实例对象,注意这个地方是对象而不是类了,这个视图是用来发现大内存对象的。这些对象都可以展开查看更详细的信息,可以看到该对象内部包含的对象:

这个视图会展示一些可能的内存泄漏的点,比如上图上图显示有3个内存泄漏可疑点,我们以Problem Suspect 1为例来理解该报告,首先我们来看该可疑点详细信息:

上面信息显示ImageCahe类的一个实例0xa50819f8占用了14.19%的内存,具体值为5147200字节(5147200/1024/1024=4.9M),并存放在LinkedHashMap这个集合中,然后我们点击Details跳转到更详细的页面:

这样我们就能找到在我们的app源码中造成该泄漏可疑点的地方,很容易去定位问题。

Android性能优化之常见的内存泄漏

前言

对于内存泄漏,我想大家在开发中肯定都遇到过,只不过内存泄漏对我们来说并不是可见的,因为它是在堆中活动,而要想检测程序中是否有内存泄漏的产生,通常我们可以借助LeakCanary、MAT等工具来检测应用程序是否存在内存泄漏,MAT是一款强大的内存分析工具,功能繁多而复杂,而LeakCanary则是由Square开源的一款轻量第三方内存泄漏检测工具,当它检测到程序中有内存泄漏的产生时,它将以最直观的方式告诉我们该内存泄漏是由谁产生的和该内存泄漏导致谁泄漏了而不能回收,供我们复查。

最近腾讯bugly也推出了三篇关于Android内存泄漏调优的文章:
1、内存泄露从入门到精通三部曲之基础知识篇
2、内存泄露从入门到精通三部曲之排查方法篇
3、内存泄露从入门到精通三部曲之常见原因与用户实践

关于性能优化的文章,出自Realm.io:
10 条提升 Android 性能的建议

内存泄漏

为什么会产生内存泄漏?

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

内存泄漏对程序的影响?

内存泄漏是造成应用程序OOM的主要原因之一!我们知道Android系统为每个应用程序分配的内存有限,而当一个应用中产生的内存泄漏比较多时,这就难免会导致应用所需要的内存超过这个系统分配的内存限额,这就造成了内存溢出而导致应用Crash。

Android中常见的内存泄漏汇总

单例造成的内存泄漏

单例模式非常受开发者的喜爱,不过使用的不恰当的话也会造成内存泄漏,由于单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。
如下这个典例:

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;
    

这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个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;
    

这样不管传入什么Context最终将使用Application的Context,而单例的生命周期和应用的一样长,这样就防止了内存泄漏

非静态内部类创建静态实例造成的内存泄漏

有的时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,可能会出现这种写法:

public class MainActivity extends AppCompatActivity 
    private static TestResource mResource = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(mManager == null)
            mManager = new TestResource();
        
        //...
    
    class TestResource 
        //...
    

这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确的做法为:
将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请使用ApplicationContext

Handler造成的内存泄漏

Handler的使用造成的内存泄漏问题应该说最为常见了,平时在处理网络任务或者封装一些请求回调等api都应该会借助Handler来处理,对于Handler的使用代码编写一不规范即有可能造成内存泄漏,如下示例:

public class MainActivity extends AppCompatActivity 
    private Handler mHandler = new Handler() 
        @Override
        public void handleMessage(Message msg) 
            //...
        
    ;
    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        loadData();
    
    private void loadData()
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    

这种创建Handler的方式会造成内存泄漏,由于mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏,所以另外一种做法为:

public class MainActivity extends AppCompatActivity 
    private MyHandler mHandler = new MyHandler(this);
    private TextView mTextView ;
    private static class MyHandler extends Handler 
        private WeakReference<Context> reference;
        public MyHandler(Context context) 
            reference = new WeakReference<>(context);
        
        @Override
        public void handleMessage(Message msg) 
            MainActivity activity = (MainActivity) reference.get();
            if(activity != null)
                activity.mTextView.setText("");
            
        
    

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView)findViewById(R.id.textview);
        loadData();
    

    private void loadData() 
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    

创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息,更准确的做法如下:

public class MainActivity extends AppCompatActivity 
    private MyHandler mHandler = new MyHandler(this);
    private TextView mTextView ;
    private static class MyHandler extends Handler 
        private WeakReference<Context> reference;
        public MyHandler(Context context) 
            reference = new WeakReference<>(context);
        
        @Override
        public void handleMessage(Message msg) 
            MainActivity activity = (MainActivity) reference.get();
            if(activity != null)
                activity.mTextView.setText("");
            
        
    

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView)findViewById(R.id.textview);
        loadData();
    

    private void loadData() 
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    

    @Override
    protected void onDestroy() 
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    

使用mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。当然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的Runnable和Message。

线程造成的内存泄漏

对于线程造成的内存泄漏,也是平时比较常见的,如下这两个示例可能每个人都这样写过:

//——————test1
        new AsyncTask<Void, Void, Void>() 
            @Override
            protected Void doInBackground(Void... params) 
                SystemClock.sleep(10000);
                return null;
            
        .execute();
//——————test2
        new Thread(new Runnable() 
            @Override
            public void run() 
                SystemClock.sleep(10000);
            
        ).start();

上面的异步任务和Runnable都是一个匿名内部类,因此它们对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成,
那么将导致Activity的内存资源无法回收,造成内存泄漏。正确的做法还是使用静态内部类的方式,如下:

    static class MyAsyncTask extends AsyncTask<Void, Void, Void> 
        private WeakReference<Context> weakReference;

        public MyAsyncTask(Context context) 
            weakReference = new WeakReference<>(context);
        

        @Override
        protected Void doInBackground(Void... params) 
            SystemClock.sleep(10000);
            return null;
        

        @Override
        protected void onPostExecute(Void aVoid) 
            super.onPostExecute(aVoid);
            MainActivity activity = (MainActivity) weakReference.get();
            if (activity != null) 
                //...
            
        
    
    static class MyRunnable implements Runnable
        @Override
        public void run() 
            SystemClock.sleep(10000);
        
    
//——————
    new Thread(new MyRunnable()).start();
    new MyAsyncTask(this).execute();

这样就避免了Activity的内存资源泄漏,当然在Activity销毁时候也应该取消相应的任务AsyncTask::cancel(),避免任务在后台执行浪费资源。

资源未关闭造成的内存泄漏

对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

一些建议

1、对于生命周期比Activity长的对象如果需要应该使用ApplicationContext
2、在涉及到Context时先考虑ApplicationContext,当然它并不是万能的,对于有些地方则必须使用Activity的Context,对于Application,Service,Activity三者的Context的应用场景如下:

**其中:**NO1表示Application和Service可以启动一个Activity,不过需要创建一个新的task任务队列。而对于Dialog而言,只有在Activity中才能创建
3、对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏
4、对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:

  1. 将内部类改为静态内部类
  2. 静态内部类中使用弱引用来引用外部类的成员变量

5、对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null
6、保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期

以上是关于性能优化工具-MAT的使用的主要内容,如果未能解决你的问题,请参考以下文章

内存泄漏--2 性能优化工具MemoryAnalyzer(MAT)内存泄露的简单检测

Android性能优化之常见的内存泄漏

Android内存优化工具

Android内存优化工具

Android内存优化工具

Linux性能优化方向及相关工具