zoukankan      html  css  js  c++  java
  • Android Studio和MAT结合使用来分析内存问题

    转载 来源:zxm317122667的专栏,https://www.2cto.com/kf/201608/536175.html

    Android开发中时常会遇到内存泄漏的问题,而Android系统对单个App又有一定的内存限制,此值可以通过一下方式获取:

    1
    2
    3
    <code>ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
    intmemoryClass = am.getMemoryClass();
    </code>

    上述代码中momeryClass的值可以当做每个App的内存限制。这个值根据不同的设备厂商都是不一样的,比如我的模拟器的值是32M,如果在我的模拟器上运行的一个App,分配的内存空间超过32M,则会报OOM(内存溢出)!而内存泄漏也是一个导致内存溢出的隐患,因此必须掌握解决内存溢出的方法。

    本章主要讲解使用Android Studio查看是否有内存泄漏问题,然后使用MAT(Memory Analyzer Tool)来分析并解决内存泄漏问题。

    我们接下来先来熟悉下Android Studio的界面 
    这里写图片描述

    dump成功后会自动打开 hprof文件,文件以Snapshot+时间来命名 
    这里写图片描述

    Android Studio分析是否有内存泄漏


    打开Android Studio中的Android Monitor中的Memory面板,可以看到有一个实时变化的堆内存曲线图,如下图所示
    这里写图片描述

    上图中重点列出了3部分内容终止检测的开关,没什么实质性的作用

    InitGC就是手动调用GC,我们在抓内存前,一定要手动点击 Initiate GC按钮手动触发GC,这样抓到的内存使用情况就是不包括Unreachable对象的(Unreachable指的是可以被垃圾回收器回收的对象,但是由于没有GC发生,所以没有释放,这时抓的内存使用中的Unreachable就是这些对象)

    DumpHeap获取hprof文件(hprof文件是我们使用MAT工具分析内存时使用的文件),但这里直接产生的文件MAT还不能直接使用,需用转换成标准的hprof文件。可以使用AndroidStudio转换或者用hprof-conv命令转化,网上可以查到

    AllocTrack开始分配追踪,第一次点击可以指定追踪内存的开始位置,第二次点击可以结束追踪的位置。这样我们截取了一段要分析的内存,等待几秒钟AndroidStudio会给我们打开一个Allocation视图(感觉和MAT工具差不多,不过MAT工具更加强大,我们也可以获取hprof文件,使用MAT来分析)


     

    写一段代码动态演示一下:

    xml布局文件如下,定义一个Button,并设置onClick属性

    1
    2
    <code><!--?xml version="1.0"encoding="utf-8"?-->
    <linearlayout android:layout_height="match_parent"android:layout_width="match_parent"android:orientation="vertical"xmlns:android="https://schemas.android.com/apk/res/android"><button android:layout_height="wrap_content"android:layout_width="wrap_content"android:onclick="click"android:text="测试Memory Monitor"></button></linearlayout></code>

    Activity代码如下:声明Button被点击回调的click方法

    1
    2
    3
    4
    5
    6
    <code>publicvoidclick(View view) {
            for(inti = 0; i < 10000; i++) {
                ImageView imageView = newImageView(this);
                list.add(imageView);
            }
    }</code>

    通过上面代码,可以预见,每次点击Button时,都会动态生成10000个ImageView并添加到List中保存起来,Memory的效果图如下:
    Memory2
    可以看到,刚开始系统分配了2M左右的内存,当点击一次Button之后,内存增加到8M,再次点击内存增加到24M左右。
    上述情况下,当我们按下返回退出Activity时,然后点击Init GC按钮执行垃圾回收操作,进程中的内存会重新回到2M,如下图:
    Memory3
    这种情况下,代码是安全稳定的代码,但是如果Activity中有内存泄漏会是何种情况呢,接下来我们先把之前的代码修改一下,认为构造一个内存泄漏的场景,如下代码所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <code>publicclassMainActivityextendsAppCompatActivity {
     
        privateList<imageview> list = newArrayList<>();
     
        staticMemoryLeak memoryLeak;
     
        @Override
        protectedvoidonCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
     
            if(memoryLeak == null) {
                memoryLeak = newMemoryLeak();
            }
     
        }
     
        publicvoidclick(View view) {
            for(inti = 0; i < 10000; i++) {
                ImageView imageView = newImageView(this);
                list.add(imageView);
            }
        }
     
        classMemoryLeak {
            voiddoSomeThing() {
                System.out.println("Wheee!!!");
            }
        }</imageview></code>

    可以看到,在MainActivity中,添加了一个非静态内存类MemoryLeak,然后声明了一个静态MemoryLeak引用。
    运行上述代码,然后再次执行点击Button的操作,可以看到内存同样会上升到8M左右,再次点击上升到16M左右,但是此时按下返回按钮并执行垃圾回收操作之后,Allocated + Free的总空间并没有重新回到2M左右,而是一直徘徊于8M左右 说明存在内存泄漏!!! 但是为什么会是8M呢??

    Android Studio生成内存字节文件


    刚才在介绍Studio的Memory面板时,有提到一个工具栏Dump Java Heap,通过点击此按钮就可以导出一个hprof文件,此过程会比较慢,需要耐心等待,当下图中心的圆圈停止转动之后hprof文件也就导出成功
    这里写图片描述

    导出完成后将自动打开这个文件,如下图所示:
    hprof
    点击Analyzer Tasks右边的绿色运行箭头,Android Studio会自动的根据此hprof文件分析有哪些类是有内存泄漏的,如下图所示:
    mmm
    确实有一个MainActivity存在内存泄漏的情况,但是跟我之前预想的有一点出入,本来以为向网上很多人说的那样,每次打开一个MainActivity时都会造成内存泄漏,但是现在事实就摆在眼前。仔细想了一下也恍然大悟了,MemoryLeak在第一个MainActivity中被声明是static静态的,当第二个被打开的MainActivity并不会再重新初始化MemoryLeak对象了,因此static MemoryLeak对象在内存中只是持有了第一个MainActivity的对象的引用,因此当我们调用多次GC操作之后,实际上只有第一个MainActivity不会被GC回收掉!!

    如果再将Activity的代码修改一下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    <code>packagematerial.danny_jiang.com.adbmemoryanalyze;
     
    importandroid.app.ActivityManager;
    importandroid.content.Context;
    importandroid.support.v7.app.AppCompatActivity;
    importandroid.os.Bundle;
    importandroid.util.Log;
    importandroid.view.View;
    importandroid.widget.ImageView;
     
    importjava.util.ArrayList;
    importjava.util.List;
     
    publicclassMainActivityextendsAppCompatActivity {
     
        privateList<imageview> list = newArrayList<>();
     
        //private static MemoryLeak memoryLeak;
     
        @Override
        protectedvoidonCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
     
            //memoryLeak = new MemoryLeak();
     
        }
     
        publicvoidclick(View view) {
            for(inti = 0; i < 10000; i++) {
                ImageView imageView = newImageView(this);
                list.add(imageView);
            }
     
            newThread() {
                @Override
                publicvoidrun() {
                    super.run();
                    while(true) {
                        try{
                            System.out.println("Thread running!!");
                            Thread.sleep(300);
                        }catch(InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
        }
     
        classMemoryLeak {
            voiddoSomeThing() {
                System.out.println("Wheee!!!");
            }
        }
    }
    </imageview></code>

    可以看到,我讲造成内存泄漏的场景由内部类改成了内部线程类,并且在线程中无限循环打印log。

    再次执行进入MainActivity–返回键–进入MainActivity–返回键的操作

    然后再生成hprof文件并打开,并执行Analyzer Tasks,可以看到如下图片的信息:
    yyy
    上图可以看出打开的每一个MainActivity都会造成内存泄漏。 擦嘞!!为什么这会儿又是这种情况呢???这个问题就牵涉到Java中线程的问题了—Java中的Thread有一个特点就是她们都是直接被GC Root所引用,也就是说Dalvik虚拟机对所有被激活状态的线程都是持有强引用,导致GC永远都无法回收掉这些线程对象,除非线程被手动停止并置为null或者用户直接kill进程操作。看到这相信你应该也是心中有答案了吧 : 我在每一个MainActivity中都创建了一个线程,此线程会持有MainActivity的引用,即使退出Activity当前线程因为是直接被GC Root引用所以不会被回收掉,导致MainActivity也无法被GC回收。所以当使用线程时,一定要考虑在Activity退出时,及时将线程也停止并释放掉


    1、移动学习在主界面时按如下顺序点击:

    2、其实和 android内存分析 outOfMemoryError错误定位及分析策略(非显示图片造成) 中用eclipse前7步的设置一样,只不过这个速度更快一些,更方便一些(eclipse ddms模式下卡的要死要死~~~~)

    3、用mat for mac(下载地址:http://www.eclipse.org/mat/downloads.php) 打开hprof文件:

    4、按Shallow Heap降序排列后,如上图所示 

    Shallow Heap代表:对象本身占用内存的大小,不包含其引用的对象。

    Retain Heap代表:如果这个对象被释放掉,那会因为该对象的释放而减少引用进而被释放的所有的对象大小。

    直观的说就是MainNewActivity占了312字节的内存,如果被释放掉的话它所持有的对象也会被释放掉共大于等于2888字节内存。

    5、在MainNewActivity右键->List Objects会出现两个选项:with outgoing references 和 with incoming references

    with outgoing references是指 被该对象引用的对象 (这个对象持有了哪个对象)

    with incoming references是指 引用到该对象的对象(哪个对象持有了该对象)

    点击with outgoing references后如图:

    6、显示出MainNewActivity所持有的所有成员变量,activityInfo的所有信息等等,点击其中的termEnd右键with incoming references,列出所有持有该对象的对象:

    7、即可看到LoadActivity、SettingsActivity、LoginActivity都持有该变量(该变量写在了BaseActivity里),大问题啊,改!

    8、GC Roots:调用该对象的根节点,例子如图:

    9、如果在object4上右键 path to GCRoot -> exclude all phantom/weak/soft etc. references(去除所有的虚引用,弱引用,软引用) 就会得到调用该类的跟节点gc root,例如:

    10、可以看出是在DBManagerHelper中实例化sqliteOpenHelper时初次调用了sqliteOpenHelper。此方法在查询对象在哪里最初被调用或持有非常好使。

    11、移动学习进入主程序后,LoadActivity理应被释放,但是还常驻内存,查了一下incoming references,发现是common.commonContext对象持有了该对象,造成了内存泄漏,修改LoadActivity和BaseActivity中的代码

     

  • 相关阅读:
    人生苦短,我用python-- Day8
    人生苦短,我用python-- Day7
    人生苦短,我用python-- Day6 面向对象
    人生苦短,我用python-- Day5
    人生苦短,我用python-- Day4
    人生苦短,我用python-- Day3
    人生苦短,我用python-- Day2
    人生苦短,我用python-- Day1
    小康陪你学JAVA--------三大循环之Do-while循环
    小康陪你学JAVA--------三大循环之For循环
  • 原文地址:https://www.cnblogs.com/chenxibobo/p/9591049.html
Copyright © 2011-2022 走看看