android内存优化之三内存分析工具的使用

来源:互联网 发布:成本估算软件 编辑:程序博客网 时间:2024/06/05 12:12

 anroid内存分析工具的使用

一.Eclipse Heap分析内存泄露

          Android开发中避免不了碰到内存泄露问题,这里先大概讲下内存泄露的基本概念:内存泄露官方的解释是是用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元,直到程序结束。它也可以理解为new的新对象用完后,该对象没有得到回收,造成的无用的对象一直占据着内存,这种无用的随着操作的次数越多,占据的内存越多,直到内存溢出程序,报错停止运行。内存溢出问题比起程序直接报错的问题更难定位,光靠阅读代码来分析内存溢出问题工作量也有些大,所以我们就不得不借助工具分析内存溢出问题。这一章节介绍的主要是如何使用内存分析工具MAT。我们用的最多的一般都是Eclipse自带的DDMS工具,进入DDMS后其界面如下:

 

       这里先讲下该工具的使用步骤:

 1. 通过USB线连接手机到PC上,android调试终端开启需要进行内存分析的APP,点击DDMS选项,进入堆显示界面。

 2. 在Devices界面选择APP的进程名,如图所示我的APP进程名是com.example.oomtest。

 3. 接着点击如上图所示的左上角绿色按钮UpdateHeap,用于更新显示APP的内存堆详情,点击后出现上图右边的Heap界面,即APP的堆的使用情况。其中Heap Size即APP的堆大小,是可变,至于上限是怎么设定的可以看下上一章节。Allocated即当前APP分配出去的堆大小,一般进行某个操作后是否内存泄露可以通过查看Allocated是否增大进行简单的判定,但是不一定准确,但是对于明显的内存泄露还是可以的,比如加载大图片并显示时。

 4. 上图右边的Heap界面,有个Cause GC按钮是用于触发你的手机的GC线程进行内存回收。这里要提下的是一些人在监听某个界面的堆情况时,进入某个界面退出后根本没有点击Cause GC,此时发现退出界面后Allocated大小增大了,以为就是内存泄露了。其实不是的,只是有时GC线程自己还没扫描到你的APP,此时该APP的堆是还没有被回收的,所以Allocated的大小比之前的增加了。所以在进行某个操作后,要查看APP的堆情况还要点击Cause GC,建议多点击几次。

 5 . 步骤5的Dump HPROF file按钮是用于下载heap内存分析的文件,接着要讲的分析工具讲用到该文件。

       看到这里可能有些网友会问了,有些代码明明是内存泄露了,为什么Allocted还是显示正常的,即操作前和操作后,Allocted大小都没变化。是的,这里也是使用DDMS查看内存泄露的缺点,一些隐蔽性比较强的内存泄露,用DDMS是看不出来的。所以第二节要讲的是如何通过Memory Analyer工具进行内存泄露分析,该方法会准确很多,也方便问题的定位。

二. Memory Analyer Tools的Program supect分析内存泄露

       这里要介绍的工具不是Eclipse自带的,所以需要下载Memory Analyer Tools(简称MAT,也可以作为插件集成到Eclipse中),本人是自己下载Memory Analyer工具。安装好Memory Analyer工具后,接着讲操作步骤:

1.  点击上图中的步骤5中的Dump HPROF file按钮下载堆分析文件,即后缀名为hprof的文件。

2.  由于下载的hprof文件在MAT中无法正常读入,所以需要sdk中的hprof-conv进行转换才可以在MAT正常读入。为了方便文件转换,接着进入sdk中查看hprof-conv工具放在什么位置,建议未转换前的hprof文件跟hprof-conv放在同一目录下,因为接着在cmd中进行转换时不用输入太长的文件路径。本人这里的hprof-conv工具的绝对路径为D:\ProgramFiles\eclipse\Android-sdk-windows-full\platform-tools>hprof-conv,同时也把转换前的hprof文件也放到这个位置。

3.  打开cmd,进入到hprof-conv所在的目录下,输入hprof-conv  old.hprof  new.hprof后按回车键,接着在D:\ProgramFiles\eclipse\Android-sdk-windows-full\platform-tools目录下生成的new.hprof文件,即为MAT可以读入的hprof文件。注:其中old.hprof为转换前的文件名,new.hprof为转换后的文件名。

4.  打开MAT,并导入转换后的hprof文件,操作如下图右上角所示:

 

5.  导入后的界面如下图所示,界面上的Problemsupect就是MAT帮你找出来的可能内存泄露的地方,看到这里你可能会想,这样很方便啊,MAT都帮你把所有可能内存泄露的地方都找出来了,接着点击Problem Supect一个一个慢慢看就可以了,其实网上很多贴也是这么说的。在这里我想说通过Problem Supect来定位某些内存泄露问题(比如加载大图片或者加载很多小图片的情况下)也是一种方法,但是此方法跟Eclipse的Heap来分析内存泄露一样,也是不准确的。

   

三 . 使用MAT准确有效的分析方法

       下面重点介绍下另一种MAT分析内存泄露的方法,到这里你可能又会有疑问了:什么样的内存泄露是上面介绍的两个方法可能检测不出来的?这里也顺便说下吧。

(1). Activity的context被生命周期更长的对象(内部类和静态变量等)占据,导致的内存泄露问题。

(2). Cursor用完没有colse造成的内存泄露问题。

        这里先介绍下几个基本概念,后面会用到: (1)Shallow Heap是对象本身占据的内存的大小,不包含其引用的对象。对于常规对象(非数组)的Shallow Size由其成员变量的数量和类型来定,而数组的ShallowSize由数组类型和数组长度来决定,它为数组元素大小的总和。(2)Retained Heap为当前对象大小+当前对象可直接或间接引用到的对象的大小总和。(间接引用的含义:A->B->C,C就是间接引用) ,并且排除被GC Roots直接或者间接引用的对象。

       操作步骤:



(1).步骤1,如上图所示,点击Overview选项,进入到主界面。

(2).步骤2,如上图所示,点击Histogram选项,进入到Histogram界面,如下图所示。其中进程名输入框用于输入APP的进程名,从而可以查看APP的堆情况。

 

       下面用一个内存泄露例子简单分析下,在Activity中定义一个线程内部类,并在线程中长时间休眠,该测试APP的进程名为com.example.oomtest,关键代码如下:

protectedvoid onCreate(BundlesavedInstanceState) {

        // TODO Auto-generated method stub

        super.onCreate(savedInstanceState);

        Log.i(TAG,"onCreate");

        setContentView(R.layout.activity_second);

        new TestThread().start();

}

publicclass TestThreadextends Thread {

        @Override

        publicvoid run() {

           super.run();

           try {

               Thread.sleep(1000 * 60 *1000);

           }catch (InterruptedExceptione) {

               e.printStackTrace();

           }

       }

 }

        进入测试界面并退出该界面操作一次,我们在获取hropf文件之后,转换并导入MAT中,打开Histogram界面后,在<Regex>中输入apk的进程名字,比如本程序使用的是com.example.oomtest,接着界面会列出APP的堆情况,如下图所示。

 


       从上图中的Objects列中可以看出当退出测试界面SencondActivity时,它的实例存在。所以可以判定SencondActivity发生了内存泄露,但是使用前面介绍的两种方法根本就分析不出APP发生了内存泄露。到这里你可能也会问,知道了SencondActivity发生了内存泄露,那么怎么知道具体是哪里发生了内存泄露?这个也简单,这也是Histogram分析内存泄露问题的优势,操作步骤如下图所示。


跟着上图所示操作,接着出现以下界面,从下图中可以看出activity实例没有被回收,这就是前面所说的隐藏性比较强的内存泄漏类型1了,主要原因就是Activity中的线程一直占住了Activity的this造成的内存泄露,所以当该Activity被Destroy时,Activity的实例没法被GC回收。

         现将线程的休眠时间修改为100时,按同样的操作方法测试,SencondActivity的子线程由于休眠时间比较短,此时当Activity被Destroy时,Activity的this已经被释放了,所以不会发生内存泄露的情况,其堆内存情况如图所示。

        从列表中可以看出此时SencondACtity的Object(对象)为0,即SencondActivity没有存在对象,所以用该方法进行内存泄露分析是很准确的,但是同样的程序通过前面的两种方法就无法定位出哪里出问题,甚至检测不出内存泄露了。这一章就先介绍到这里吧,下一章节将介绍其他一些隐藏性比较强的内存泄露问题,以便各大网友开发出高质量的APP

 

1 0