anroid内存分析工具的使用
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的load heap按钮是用于下载heap内存分析的文件,接着要讲的分析工具需要用到该文件。
看到这里可能有些网友会问了,有些代码明明是内存泄露了,为什么Allocted还是显示正常的,即操作前和操作后,Allocted大小都没变化。是的,这里也是使用DDMS查看内存泄露的缺点,一些隐蔽性的内存泄露,用DDMS是看不出来的。所以第二节要讲的是通过Memory Analyer工具进行内存泄露分析,这样会准确很多,也很方便问题的定位。
先讲下这个工具不是Eclipse自带的,所以需要自己下载Memory Analyer Tools(简称MAT,也可以作为插件集成到Eclipse中),本人是自己下载Memory Analyer工具。安装好Memory Analyer工具后,接着讲操作步骤:
1. 点击上图中的步骤5中的load heap按钮下载堆分析文件,即后缀名为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分析内存泄露的方法。到这里你可以又会有疑问了什么样的内存泄露是上面介绍的两个方法可能检测不出来的,这里也顺便说下吧。
(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(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
Log.i(TAG, "onCreate");
setContentView(R.layout.activity_second);
new TestThread().start();
}
publicclass TestThread extends Thread {
@Override
publicvoid run() {
super.run();
try {
Thread.sleep(1000 * 60 *1000);
} catch (InterruptedException e) {
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。