Memory Analyzer (MAT) 使用

2022-07-29,,

The Eclipse Memory Analyzer is a fast and feature-rich Java heap analyzer that helps you find memory leaks and reduce memory consumption.

Use the Memory Analyzer to analyze productive heap dumps with hundreds of millions of objects, quickly calculate the retained sizes of objects, see who is preventing the Garbage Collector from collecting objects, run a report to automatically extract leak suspects.

 

1. MAT:导出Bitmap

https://blog.csdn.net/nupt123456789/article/details/44018119

关于MAT的使用,请参见博客:http://blog.csdn.net/nupt123456789/article/details/42584269
 1.首先打开MAT的Inspector页面,在Eclipse中为:Open Window > ShowView > Other.. > Inspector
 2.选中图片对象android.graphics.Bitmap,在Inspector查看图片的信息,如下图:

 我们需要注意的参数是mHeight和mWidth,即,图片的高度和宽度
 3.导出Bitmap的mBuffer数据,保存为xxxx.data。如下图:
 
 此时,我们已经获取到了图片的数据文件xxxx.data和图片的宽高mWidth和mHeight。接下来就是使用工具,将这些数据转化为图片即可。
 
 4.下载GIMP软件,mac上的下载地址:http://gimp.lisanet.de/Website/Download.html
 5.运行GIMP,“文件”->“打开”,选中之前保存的xxxx.data文件,点击打开,然后根据mWidth和mHeight修改参数,图像类型选择“RGB Alpha”

 如果不能正确打开,调整其他参数。

 

2. MAT分析器中的shallow and retained heap详解

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:https://blog.csdn.net/u013309870/article/details/52038407

在MAT分析中两个特别重要的参数shallow heap 和retained heap是帮助我们分析内存的重要依据。一个对象的shallow heap的大小指的是该对象在没有引用其他对象的情况下本身占用的内存大小。一个普通对象的shallow heap 的大小(不包括数组类型)依赖于它含的方法,元素的大小。而一个数组类型的shallow  heap的大小则依赖于数组的长度和数组里面元素的类型。集合类型的shallow heap的大小则指的是集合所包含的所有对象的大小的总和。这么说有点抽象,下面拿一个实例来分析。

public class Person {
public int age;
public String name;
public double height;
public Person(int age,String name,double height)
{
this.age=age;
this.name=name;
this.height=height;
}
 
}
public class MyoomTest{
 
 
public static void main(String[] args) {
List<Person> users=new ArrayList<Person>();
int i=0;
while(true)
{
users.add(newPerson(24,"hanking",1.72));
i++;
System.out.println("i=:"+i);
}
 
}
 
}
 

 

在MyoomTest中引用了UserBean和Person两个类。同时为了尽快出现oom应该把run as中run configuration中的Arguments中的VmArguments中输入
-Xms5m -Xmx5m -XX:+HeapDumpOnOutOfMemoryError,其中的-Xms5m -Xmx5m –XX指的是虚拟机的内存,这里把虚拟机的最大内存和最小内存都设置为5m只要运行内存超过了5m 就会报outofmemory异常。其中eapDumpOnOutOfMemoryError是为了在产生内存溢出的时候保存hprof文件,hprrof文件是MAT分析所使用的文件。
在我的机器上已经跑出了上面MyoomTest的内存溢出hprof文件。现在截取在MAT中的一个片段进行分析。 
上面出现的Person包含一个int类型的age,一个Sting类型的name,和一个double类型的height.其中的对象头占用12B,int类型占用4B,double 类型占用8B,String类型默认占用8B所以一个person占用的内存是32B,所以shallow heap的大小是32B。现在分析Java.lang.Object[160065]的shallow  heap的大小,数组中一共有160066个person,但是数组中的每一个只保存了一个大小为4B的地址值,所以620280/4=160070,160070-160066=4;
剩下的4个是数组头的开销。
retained heap是指对象自己本身的shallow heap的大小加上对象所引用的对象的大小。换句话说retained heap的大小是指该对象被回收时垃圾回收器应该回收的内存的大小。
垃圾回收器在回收垃圾是不仅回收该对象还要回收该对象所引用的对象。为了更好地而理解retained heap我们来看看下面的例子:下图中的节点代表着对象,带有箭头的边代表着对引用关系。 
上图中用蓝色标记的对象表示直接或间接被obj1所引用的对象。图1中我们看到obj3并没有标记成蓝色因为它是被Gc Root直接引用的。图2中的obj3未被Gc Root直接引用所以被标记成蓝色。因此obj1的retained heap的大小在figure1中指的是:
Obj1+ Obj2+Obj3;在figure2中指的是:Obj1+ Obj2+ Obj3+ Obj4;
现在来分析Java.lang.Object[160065]的retained heap的大小在垃圾回收器回收时回收的大小为Java.lang.Object[160065]的shallow heap的大小加上160066个person的shallow heap的大小:620280+32*160066=5742392B与上面的5762360B相差不大,因为还有可能其他地方会占用一些内存。
本文参考文献:http://jingyan.baidu.com/article/6b1823096dbcf9ba58e15999.html

 

3.MAT排查Android Activity内存泄露

https://blog.csdn.net/u012735483/article/details/52434858

一、先磨刀再砍柴,内存泄露相关介绍

  我们先来简单重温一下Java GC 的概念:GC即为Garbage Collection,垃圾回收机制。Java GC实质上也就是一个运行在Java虚拟机(JVM)上的一个程序,它自动地管理着内存的使用,在适当的时机释放并回收无用的内存分配。使得我们不用像写C++那样手动释放内存,从而帮助我们释放双手。那它是如何知道哪些内存分配是无用的,而哪些是有用的呢?借用一下2011年Google I/O的一张图:

  垃圾回收机制设置了一个根节点GC Roots,然后从根节点开始往下进行遍历,凡是能被直接或间接访问到的子节点,都认为其是仍然有用的,不应该被回收的,也就是图中的黄色节点。而无法被遍历到的蓝色节点,即被认为是无用的,应该被回收的内存区域。
  那什么是内存泄露呢?简单来说就是:不应该被GC Roots访问到到的内存,仍能被访问到,GC Roots误以为这块内存区域并不是垃圾,导致该回收的内存没被回收。久而久之,内存泄露越来越严重,旧的垃圾内存得不到回收,新的垃圾内存不断增加,可用的内存也就越来越少。JVM为了申请新的内存空间,频繁触发GC,程序执行效率将会受到影响,程序甚至直接抛出Out Of Memory Exception异常退出。

  下面我们来熟悉一下Android开发中容易出现的内存泄漏场景:

参考:http://www.jianshu.com/p/a50ea6333677

1、静态变量引用Activity

  静态变量是驻扎在JVM的方法区,因此,静态变量引用的对象是不会被GC回收的,因为它们所引用的对象本身就是GC ROOT。如果实在要用这样别扭的写法,记得在onDestroy()里把mActivity置为null。

public class TopicDetailActivity extends AppCompatActivity {
    private static Activity mActivity;

    public static void enterActivity(Activity activity) {
        mActivity = activity;
        Intent intent = new Intent(activity, TopicDetailActivity.class);
        activity.startActivity(intent);
    }
}

2、非静态内部类

  非静态内部类(包括匿名内部类)默认会持有外部类实例,如果内部类的生命周期长于外部类,则可能导致内存泄漏。可以考虑改成静态内部类,或者新创建一个文件,把内部类挪过去。

public class MainActivity extends AppCompatActivity {  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        MyThread myThread = new MyThread();  
        myThread.start();  
    }  

    class MyThread extends Thread {  
        @Override  
        public void run() {  
            while (true) {  
                try {  
                    Thread.sleep(65536);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }  
    }  
}  
  •  

3、Activity内View不被释放

  一旦View attach到我们的Window上,就会持有一个Context(即Activity)的引用。如果View无法被释放,Activity也无法被释放。

public class MainActivity extends AppCompatActivity {

    private static TextView tv;

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

        tv = (TextView) findViewById(R.id.tv);
        tv.setText("Hello world");
    }
}

4、Handler

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {

            }
        };
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {

            }
        }, Integer.MAX_VALUE);
    }
}

  同第二点,代码中new了一个Handler的匿名内部类,默认持有Activity。我们知道,主线程的Looper对象不断从消息队列中取出消息,然后再交给Handler处理。而每个Message对象是持有Handler的引用的(Message对象的target属性持有Handler引用),从而导致Message间接引用到了Activity。如果在Activty destroy之后,消息队列中还有Message对象,Activty是不会被回收的。同样是,可以把匿名内部类改成静态内部类,或挪到另一个文件中去。还可以在onDestroy()时,removeCallbacks或removeCallBacks来清除MessageQueue残留的Message,已保证Activity不会被Message hold住。

5、监听器

  在Activity里注册BroadcastReceiver或EventBus等忘记反注册。

….更多详情看http://www.jianshu.com/p/a50ea6333677

二、步入正题,学用MAT

MAT是一种快速,功能丰富的Java堆分析工具,用于分析hprof文件,帮助你查找内存泄漏和减少内存消耗。

1、获取hprof文件

  首先,我们打开Android Studio,调试好ADB相关,并run起我们的工程,这里确保一下我们的工程存在内存泄漏的Activity(- -因为如果没有内存泄漏存在,那就没得分析啦,巧妇难为无米之炊啊)。我们不停地在页面间进行跳转,最后回到起始的MainActivity。然后,在AS的左下角可以找到一个名为”Android Monitor”的栏目,点开之后再从我们常用的”logcat”切换到”Monitors”下,此时可以看见已连接手机的内存、CPU、GPU等使用情况。点击”Memory”项里红框选中的按钮”Dump Java Heap”。

  稍等片刻,我们就会得到一个“不完整”的hprof文件,AS会自动帮我们打开这个文件。(注意,这个hprof还不能用MAT打开,强行用MAT打开会报错!)

  点击右边的Analyzer Tasks,会有意外的惊喜哟~没错!AS居然自带了方便我们排查Activity内存泄露的工具。

2、意外的收获,AS自带的分析工具

  勾选”Detect Leaked Activities”,然后点击绿色的”>”按钮,分析结果就出来了。展开分析结果里的”Leaked Activities”就可以看到哪些Activity存在内存泄漏问题。这里可以看出TopicDetailActivity明显就泄漏了,因为我dump hprof文件时,已经回到起始的MainActivity了且Activity栈不存在TopicDetailActivity的实例了,所以内存中不应该有那么多TopicDetailActivity的实例:@601142272,@603487232等。这些实例都应该被GC回收,而没有被回收!查找内存泄漏是不是So easy啊~。嗯(⊙_⊙),虽然每天用AS都卡得飞起,但是这个分析工具还是值得肯定的,方便,准确!

  好了,我们现在知道,哪些Activity被泄漏了,但是我们仍然不知道它们为什么会被泄漏。刚开篇的时候,我们说到,能被GC Roots直接或间接访问到的实例是不会被回收的。Activity泄漏和其他内存泄漏一样,都是因为被某个长时间不会被销毁的家伙给引用了,而这个家伙能被GC Roots访问到。接着,我们就来看看是谁hold住了Activity又不肯放手。

  选中某个已知泄漏的Activity,可以查看其Reference Tree。这里列出了所有引用TopicDetailActivity的对象,一眼望过去,图中引用TopicDetailActivity实例的对象有View类的,比如TextView,也有非View类,比如ContextImpl。这里注意一下,AS会把重点怀疑对象用蓝色字体标示出来,这里标出来的是CursorWatcherEditTextView(这里声明一下,它是EditText的子类),泄漏的原因八九不离十就是这个自定义View。那又是什么导致CursorWatcherEditTextView Hold住了Activity呢。这里再继续展开Reference Tree也不容易看出个所以然来。要想进一步深入还得使用MAT。
  我们来导出一个”完整版”的hprof。点击AS左上角的歪脖”Captures”,然后按图示”Export to standard.hprof”,选好导出位置再导出就好了

3、主角登场,MAT使用简介

  接下来我们用MAT打开hprof文件:

  注意红框里圈出的两个Action,分别是用两种不同角度查看内存的使用情况。
  Histogram根据以class为基本单位,来列举每个类各有多少个实例(Objects),Shallow Heap代表这个类的所有实例占有的内存大小。然后也没啥好看的了,我们再去看看Dominator Tree。

  Dominator Tree会从大到小列举出一些当前内存中占用最大内存的对象。Shallow Heap的概念与Histogram里的一样。Retained Heap代表如果这个对象被回收,有多少内存能被真正释放,其中包括了Shallow Heap(自身占有的内存) + 只被这个对象Hold住的其他对象。Percentage则表示占比,这很简单。

  刚才我们已经通过AS分析工具,得知TopicDetailActivity这个页面存在内存泄漏,故在搜索框里输入TopicDetailActivity。在以上两种Tab下,选中某一栏右键都能看到一系列的操作。

  这里我们只介绍一下”List Object”和”Merge Shortest Paths to GC Roots”,其他的大家自己去摸索哈。
  List Object也有两个可选的“with outgoing reference”:查看这个对象它引用了谁,”with incoming reference”:查看是谁引用了这个对象,又是两种不同的角度。排查内存泄漏,我们肯定是看incoming,因为只有被引用才可能被hold住,才可能内存泄漏。放上incoming的图,大家自行感受一下。

  由上图可见,总共有306个实例引用了TopicDetailActivity,一个个排查过去不得爆炸..所以我们化繁为简,用”Merge Shortest Paths to GC Roots”->”exclude weak/soft references”(排除掉弱引用和软引用,因为他们不会造成内存泄漏):从GC Roots开始找一条能到达Activity的最短的路径,这往往就是内存泄漏的真正原因。

  然后逐级展开到底,我们能看到TopicDetailActivity实例。然后,简单介绍一下如何看这个图。我们从下往上看,Activity被CursorWatcherEditText hold住了,而Activity的引用被赋值在CursorWatcherEditText的mContext变量域里,同理,CursorWatcherEditText被Editor hold住了,CursorWatcherEditText的引用被赋值在Editor的mTextView变量域里,以此类推。那这个内存泄漏到底是为什么呢?这个问题可能比较复杂,我们慢慢理清逻辑。

  CursorWatcherEditText持有TopicDetailActivity这很正常,因为我们之前说过,一旦View attach到我们的Window上,就会持有其Activity的引用。我们再往上找原因。因为CursorWatcherEditText继承自EditText,EditText继承自TextView,通过看TextView.java的源码可知,在new 成员变量mEditor的时候,会把TextView实例自身传给mEditor,然后被保存在Editor实例的mTextView成员变量域内,这依旧没啥问题。

  然后我们再往上看,噫,Editor被他自己的内部类Blink hold住了,找到Blink类,这货居然不是静态的内部类,还是个Handler,还实现了Runnable,极有可能就是这个导致的内存泄露。

  我们最后往上看一次- -,一眼看完,Path上端还真的有Message,MessageQueue,就是我们之前说的因为仍有残留的Message hold住了Handler导致的内存泄漏。好了,这条Path总算是摸清了,但是新的问题又出现了,为什么会有残留的Message,是不是我们用EditText的姿势不对或者说是Extends的时候出了什么问题。虽然不太明白,但是我们进一步阅读源码后发现,Blink与Cursor以及Focus息息相关,并且TextView#setEnabled(boolean enabled)里有这么一行,会尝试调用blink.removeCallbacks:
,在onDestroy()里调用editReply.setEnabled(false);,内存泄漏得到解决

 

4.MAT使用进阶

https://www.jianshu.com/p/c8e0f8748ac0

Java的内存泄露

  • Java中的内存泄露主要特征:可达,无用
  • 无用指的是创建了但是不再使用之后没有释放
  • 能重用但是却创建了新的对象进行处理

MAT使用技巧

使用Android Studio Dump内存文件

Android Studio的最新版本可以直接获取hprof文件:

Android-Studio

然后选择文件,点击右键转换成标准的hprof文件,就可以在MAT中打开了。

在使用使用Eclipse或者AndroidStudio抓内存之前,一定要手动点击 Initiate GC按钮手动触发GC,这样抓到的内存使用情况就是不包括Unreachable对象的。

手动触发GC

Unreachable对象

Unreachable指的是可以被垃圾回收器回收的对象,但是由于没有GC发生,所以没有释放,这时抓的内存使用中的Unreachable就是这些对象。

Unreachable Objects

Unreachable Objects Histogram

点击Calculate Retained Size之后,会出现Retained Size这一列

Calculate Retained Size

Unreachable Objects Histogram

可以看到Unreachable Object的对象其Retained Heap值都为0.这也是正常的。

Histogram

MAT中Histogram的主要作用是查看一个instance的数量,一般用来查看自己创建的类的实例的个数。

  • 可以很容易的找出占用内存最多的几个对象,根据Percentage(百分比)来排序。
  • 可以分不同维度来查看对象的Dominator Tree视图,Group by class、Group by class loader、Group by package
    和Histogram类似,时间久了,通过多次对比也可以把溢出对象找出来。
  • Dominator Tree和Histogram的区别是站的角度不一样,Histogram是站在类的角度上去看,Dominator Tree是站的对象实例的角度上看,Dominator Tree可以更方便的看出其引用关系。

Histogram group by package

通过查看Object的个数,结合代码就可以找出存在内存泄露的类(即可达但是无用的对象,或者是可以重用但是重新创建的对象

Histogram中还可以对对象进行Group,更方便查看自己Package中的对象信息。

 

Paste_Image.png

Thread信息

MAT中可以查看当前的Thread信息:

 

Thread

 

从图中可以得到的信息:

  1. 可以看到可能有内存问题的Thread:

内存异常

  1. 可以看到数量可能有问题的Thread

数量异常

帮助信息

MAT中的各个视图中,在每一个Item中点击右键会出现很多选项,很多时候我们需要依赖这些选项来进行分析:

右键选项

这些选项的具体含义则可以通过右键中的Search Queries这个选项(上图中的倒数第四个选项)进行搜索和查看,非常的有用。

帮助信息

可以看到,所有的命令其实就是配置不同的SQL查询语句。

比如我们最常用的:

  • List objects -> with incoming references:查看这个对象持有的外部对象引用
  • List objects -> with outcoming references:查看这个对象被哪些外部对象引用
  • Path To GC Roots -> exclude all phantim/weak/soft etc. references:查看这个对象的GC Root,不包含虚、弱引用、软引用,剩下的就是强引用。从GC上说,除了强引用外,其他的引用在JVM需要的情况下是都可以 被GC掉的,如果一个对象始终无法被GC,就是因为强引用的存在,从而导致在GC的过程中一直得不到回收,因此就内存溢出了。
  • Path To GC Roots -> exclude weak/soft references:查看这个对象的GC Root,不含弱引用和软引用所有的引用.
  • **Merge Shortest path to GC root **:找到从GC根节点到一个对象或一组对象的共同路径

Debug Bitmap

如果经常使用MAT分析内存,就会发现Bitmap所占用的内存是非常大的,这个和其实际显示面积是有关系的。在2K屏幕上,一张Bitmap能达到20MB的大小。

所以要是MAT提供了一种方法,可以将存储Bitmap的byte数组导出来,使用第三方工具打开。这个大大提高了我们分析内存泄露的效率。

关于这个方法的操作流程,可以参考这篇文章还原MAT中的Bitmap图像.

Debug ArrayList

ArrayList是使用非常常用的一个数据结构,在MAT中,如果想知道ArrayList中有哪些数据,需要右键-> List Objects -> With outgoing references,然后可以看到下面的图:

Outgoing

从上图可以看到,这个ArrayList的内容在一个array数组中,即暴漏了ArrayList的内部结构,查看的时候有点不方便,所以MAT提供了另外一种查看ArrayList内数据的方式:

Extrace List Values

其结果非常直观:

Extrace List Values Result

Big Drops In Dominator Tree

Big Drops In Dominator Tree选项在右键->

Displays memory accumulation points in the dominator tree. Displayed are objects with a big difference between the retained size of the parent and the children and the first "interesting" dominator of the accumulation point. These are places where the memory of many small objects is accumulated under one object.

Big Drops In Dominator Tree
作者:Gracker
链接:https://www.jianshu.com/p/c8e0f8748ac0
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文地址:https://blog.csdn.net/lipanpan1030/article/details/108577067

《Memory Analyzer (MAT) 使用.doc》

下载本文的Word格式文档,以方便收藏与打印。