Android Studio和MAT结合使用来分析内存问题

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

ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
int memoryClass = am.getMemoryClass();

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

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

Android Studio分析是否有内存泄漏



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

上图中重点列出了3部分内容:

  1. 被测试的终端设置,如图所示我是在模拟器Nexus_S上做测试
  2. 被测试的进程,点击可选择其他Application或者进程
  3. 当前被测试的进程中内存分配情况
    • Allocated代表已分配的空间
    • Free代表可用剩余空间
    • Allocated + Free不能超App内存限制(32M)
  4. 内存分析的工具栏,从上向下一共4个按钮,依次是:

    终止检测的开关,没什么实质性的作用


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

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

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

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

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:text="测试Memory Monitor"
        android:onClick="click"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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

public void click(View view) {
        for (int i = 0; i < 10000; i++) {
            ImageView imageView = new ImageView(this);
            list.add(imageView);
        }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

通过上面代码,可以预见,每次点击Button时,都会动态生成10000个ImageView并添加到List中保存起来,Memory的效果图如下:

可以看到,刚开始系统分配了2M左右的内存,当点击一次Button之后,内存增加到8M,再次点击内存增加到24M左右。

上述情况下,当我们按下返回退出Activity时,然后点击Init GC按钮执行垃圾回收操作,进程中的内存会重新回到2M,如下图:

这种情况下,代码是安全稳定的代码,但是如果Activity中有内存泄漏会是何种情况呢,接下来我们先把之前的代码修改一下,认为构造一个内存泄漏的场景,如下代码所示:

public class MainActivity extends AppCompatActivity {

    private List<ImageView> list = new ArrayList<>();

    static MemoryLeak memoryLeak;

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

        if (memoryLeak == null) {
            memoryLeak = new MemoryLeak();
        }

    }

    public void click(View view) {
        for (int i = 0; i < 10000; i++) {
            ImageView imageView = new ImageView(this);
            list.add(imageView);
        }
    }

    class MemoryLeak {
        void doSomeThing() {
            System.out.println("Wheee!!!");
        }
    }
  • 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
  • 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

可以看到,在MainActivity中,添加了一个非静态内存类MemoryLeak,然后声明了一个静态MemoryLeak引用。

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

Android Studio生成内存字节文件



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

导出完成后将自动打开这个文件,如下图所示:

点击Analyzer Tasks右边的绿色运行箭头,Android Studio会自动的根据此hprof文件分析有哪些类是有内存泄漏的,如下图所示:

确实有一个MainActivity存在内存泄漏的情况,但是跟我之前预想的有一点出入,本来以为向网上很多人说的那样,每次打开一个MainActivity时都会造成内存泄漏,但是现在事实就摆在眼前。仔细想了一下也恍然大悟了,MemoryLeak在第一个MainActivity中被声明是static静态的,当第二个被打开的MainActivity并不会再重新初始化MemoryLeak对象了,因此static

MemoryLeak对象在内存中只是持有了第一个MainActivity的对象的引用,因此当我们调用多次GC操作之后,实际上只有第一个MainActivity不会被GC回收掉!!

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

package material.danny_jiang.com.adbmemoryanalyze;

import android.app.ActivityManager;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private List<ImageView> list = new ArrayList<>();

    //private static MemoryLeak memoryLeak;

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

        //memoryLeak = new MemoryLeak();

    }

    public void click(View view) {
        for (int i = 0; i < 10000; i++) {
            ImageView imageView = new ImageView(this);
            list.add(imageView);
        }

        new Thread() {
            @Override
            public void run() {
                super.run();
                while (true) {
                    try {
                        System.out.println("Thread running!!");
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }

    class MemoryLeak {
        void doSomeThing() {
            System.out.println("Wheee!!!");
        }
    }
}
  • 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
  • 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

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

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

然后再生成hprof文件并打开,并执行Analyzer Tasks,可以看到如下图片的信息:

上图可以看出打开的每一个MainActivity都会造成内存泄漏。 擦嘞!!为什么这会儿又是这种情况呢???这个问题就牵涉到Java中线程的问题了—Java中的Thread有一个特点就是她们都是直接被GC

Root所引用,也就是说Dalvik虚拟机对所有被激活状态的线程都是持有强引用,导致GC永远都无法回收掉这些线程对象,除非线程被手动停止并置为null或者用户直接kill进程操作。看到这相信你应该也是心中有答案了吧
:
我在每一个MainActivity中都创建了一个线程,此线程会持有MainActivity的引用,即使退出Activity当前线程因为是直接被GC

Root引用所以不会被回收掉,导致MainActivity也无法被GC回收。所以当使用线程时,一定要考虑在Activity退出时,及时将线程也停止并释放掉

MAT内存分析工具



正常来讲,根据上面我讲的使用Studio来分析简单的内存泄漏已经足够了,但是在Studio之前有一款更加强大的内存分析工具MAT,谷歌工程师称它更加的Powerful!!。接下来就看一下如何使用MAT来分析内存问题

1 首先在eclipse官网下载MAT工具

https://www.eclipse.org/mat/

2 下载完MAT并安装好之后,需要先生成hprof文件。

这两我还是使用之前线程造成内存泄漏的案例来演示,

  • 首先第一次打开MainActivity时,点击dump heap生成一个hprof文件
  • 其次进行一系列的操作, 比如点击Button,按下返回键,再次进入MainActivity等等,这里我重复了4遍如上操作,然后再点击dump heap生成hprof文件

3 点击Studio的Captures栏,显示刚才生成的hprof文件,如下图所示:

这两个文件我们需要使用MAT去打开并对比分析,但是MAT不能直接打开这两个文件,需要将它转换成MAT能够识别的文件,Captures栏中,右键点击每一个hprof文件,然后选择Export to standard .hprof并保存到电脑目录中,如下图:

4 使用MAT打开转换后的hprof文件,显示如下图

可以看到有两个dump的面板,其中每一个都显示了一个内存的饼状图。其中用的最多的功能是左下角的Histogram, 点击 Actions下的 Histogram项将得到 Histogram结果:

它按类名将所有的实例对象列出来,可以点击表头进行排序,在表的第一行可以输入正则表达式来匹配结果 :

在Histogram中,可以右键某一想查看的对象,然后选中List Objects来查看此对象的所有实例,如下图

选中之后,会跳出所有实例对象面板,在此面板中可以可以继续某一特定实例在内存中的Path To GC
Root(从GC开始的强引用)。在之前的案例操作中,我重复的进入MainActivity4次,并依次点击Button运行线程,因此正常来说MainActivity应该有4个实例在内存当中,如下图

exclude all phantom/weak/soft的意思是讲所有的虚引用/软引用/弱引用都排除掉,因为只有强引用才会造成内存泄漏!点击之后显示下图信息:

可以看到,MainActivity最终都是被一个叫做MainActivity1的对象引用,而MainActivity1就是在click方法中创建的匿名内部类Thread对象。
最终我们找到了内存泄漏的根本原因 : 当Activity退出之后,Thread因为被GC
Root直接引用,所以不会被GC回收掉,而Thread又持有Activity的引用导致Activity也无法被GC正常回收掉,造成了Activity的内存泄漏,大功告成!!!

如何发现内存泄漏



上面分别介绍了使用Android studio和MAT分析内存的方法。Android studio自带的内存分析工具直观方便,但其功能却不如MAT强大,特别是没有有效的搜索、排序等功能。遇到一些棘手的问题,可能还是要借助MAT来分析内存。

上面的例子是我们人为制造了一个内存泄漏,然后有意用工具检测他。但实际开发中,我们如何发现内存泄漏呢?我想可以首先使用studio自带或DDMS中的heap分析工具,观察在反复执行某个操作时(例如打开某个页面、点击某个按钮、加载某个资源等等)时,内存在执行GC后能始终维持在稳定的值附近。如果内存呈线性增长的趋势,那一定是发生了内存泄漏。此时,就要dump出内存镜像,然后使用工具分析了。

在分析内存时,第一是可以使用工具自带的泄漏检查器帮助定位。另外,可以在执行操作(怀疑造成内存泄漏的操作)前后,分别dump出一份内存镜像,然后使用MAT的Compare
Basket对比两个文件的内存情况,这样可以帮助定位到是哪个对象发生了泄漏。然后再找到这个对象的GC
Roots,这样就可以进一步定位到具体的代码了。

常见内存泄漏问题总结

请查看链接:http://blog.csdn.net/zxm317122667/article/details/52106741

时间: 2024-10-10 13:27:48

Android Studio和MAT结合使用来分析内存问题的相关文章

Android Studio下的应用性能优化总结-内存优化

转载请标明出处(请勿转载删除底部微博.微信等信息): http://blog.csdn.net/Y1258429182/article/details/51176424 本文出自:杨哲丶的博客 上一篇文章总结的布局优化的问题,如果对布局优化不是很熟悉的,可以看一下Android Studido下的应用性能优化总结–布局优化, 这周一直筹划总结一下内存优化的问题,因为现在对于应用优化的文章很多,但是还是想完善一下才想分享这篇文章的,我会从项目中遇到的一个问题,通过解决问题的过程来分享知识,希望大家

Android Studio中的AndroidManifest.xml文件分析

一.关于AndroidManifest.xml AndroidManifest.xml清单文件是每个Android程序中必须的文件,它是整个Android程序的全局描述文件,除了能声明程序中的Activities,Content Providers,Services,和Intent Receivers,还能指定应用的名称.使用的图标.包含的组件以及permissions和instrumentation(安全控制和测试). 二.Hello World工程中的清单文件分析 <?xml version

使用新版Android Studio检测内存泄露和性能

内存泄露,是Android开发者最头疼的事.可能一处小小的内存泄露,都可能是毁于千里之堤的蚁穴.  怎么才能检测内存泄露呢?网上教程非常多,不过很多都是使用Eclipse检测的, 其实1.3版本以后的Android Studio 检测内存非常方便, 如果结合上MAT工具,LeakCanary插件,一切就变得so easy了. 熟悉Android Studio界面 工欲善其事,必先利其器.我们接下来先来熟悉下Android Studio的界面  PHPer月薪测试题 [点击进入] 看看自己工资拿少

Android Studio检测内存泄露和性能

韩梦飞沙 yue31313 韩亚飞 han_meng_fei_sha [email protected] 首先需要明白一个概念, 内存泄露就是指,本应该回收的内存,还驻留在内存中. 一般情况下,高密度的手机,一个页面大概就会消耗20M内存,如果发现退出界面,程序内存迟迟不降低的话,可能就发生了严重的内存泄露. 我们可以反复进入该界面,然后点击dump java heap 这个按钮,然后Android Studio就开始干活了,下面的图就是正在dump  dump成功后会自动打开 hprof文件,

深度分析内存泄漏原因,使用MAT工具检测内存泄露和性能

造成内存泄漏原因: 场景一:静态变量导致的内存泄漏 例如:mainactivity中 private static context scontext: @override protected void oncreat(bundle savedinstancestate){ ............................................. scontext=this; } 泄漏点:静态变量scontext引用,activity无法正常销毁 场景二:单例模式导致的内存泄漏

Android Studio 1.0 (稳定版) 完全攻略

Android Studio 安装与使用 Android 1.0发布介绍: 2014年12月8日,Google正式发布了面向Android开发者的集成开发环境 Android Studio 1.0(稳定版). 从2013年5月到2014年12月,近一年零7个月,终于盼到了1.0稳定版的发布. 想必广大Android开发者们也和小编一样有种望眼欲穿,守得云开见月明的感觉吧. 小编经过一段时间的使用,已经深深爱上了这款出众的IDE.作为长期使用Eclipse+Genymotion(不得不用Genym

Android studio 百度地图开发(3)地图导航

email:[email protected] 开发环境:win7 64位,Android Studio,请注意是Android Studio,使用的导航SDK版本:3.1.0. 地图显示.工程配置请参考:Android studio 百度地图开发(1)配置工程.显示地图 百度地图定位请参考:Android studio 百度地图开发(2)地图定位 一.我为百度做点事 因为在写定位功能时自己想从头到尾地写,但最后完全是参考了百度官网上的Demo才弄出来,后来发现用Android Studio做导

mac android studio 安装

中秋节,一个人在租房里过节,闲的无聊,就把mac 上android studio环境搭建好了. 之前工作里面都是用的eclipse,开发还是挺顺畅的,不过最近google好像是在推android studio,刚好换了mac,内存够大,就尝试一下android studio吧. 1 直接下载android studio 安装包 2 安装后直接打开,但是发现一直在downloading sdk,上网说是每次studio 都会检查更新,解决: 应用程序-->android studio-->右键:

分析内存泄露问题

转自:http://rayleeya.iteye.com/blog/1956638 无论怎么小心,想完全避免 bad code 是不可能的,此时就需要一些工具来帮助我们检查代码中是否存在会造成内存泄漏的地方. 既然要排查的是内存问题,自然需要与内存相关的工具,DDMS和MAT就是两个非常好的工具.下面详细介绍. 2.3.1 内存监测工具 DDMS --> Heap Android tools 中的 DDMS 就带有一个很不错的内存监测工具 Heap(这里我使用 eclipse 的 ADT 插件,