场景:1.app运行一段时间,会出现内存增大的情况
2.执行某一个操作多次,会出现界面卡住或者界面变黑的情况
3.加载大数据进行显示预览的时候
产生内存泄露的原因:
当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。
1 内存泄漏的排查方法
Dalvik Debug Monitor Server (DDMS) 是 ADT插件的一部分,其中有两项功能可用于内存检查 :
·heap 查看堆的分配情况
·allocation tracker跟踪内存分配情况
DDMS 这两项功能有助于找到内存泄漏的操作行为。
Eclipse Memory Analysis Tools (MAT) 是一个分析 Java堆数据的专业工具,用它可以定位内存泄漏的原因。
工具地址 :
http://www.eclipse.org/mat/downloads.php
http://download.eclipse.org/mat/1.4/update-site/
1.1 观察 Heap
运行程序,然后进入 DDMS管理界面,如下:
首先要load出应用的内存快照,这里分为4步,第一步,选中我们要查看的应用,第二步点击Update Heap按钮,这时候DDMS就会通知应用准备收集内存信息,第三步选择Heap标签,heap标签页能够展示出内存的所有信息。第四步点击Cause GC,这时候就会把内存快照load出来。这样DDMS就把内存快照load出来了
Load出内存信息之后,选中data object这一行,就来分析我们应用中是否存在内存泄漏,
分析内存泄漏的关键的数据之一,就是Total Size。
主要关注两项数据:
(1) Heap Size 堆的大小,当资源增加,当前堆的空余空间不够时,系统会增加堆的大小,若超过上限 (例如64M,视平台和具体机型而定)则会被杀掉
(2) Allocated 堆中已分配的大小,这是应用程序实际占用的内存大小,资源回收后,此项数据会变小
· 查看操作前后的堆数据,看是否有内存泄漏
对单一操作(比如添加页,删除页)进行反复操作,如果堆的大小一直增加,则有内存泄漏的隐患。
1.2使用内存工具MAT分析
有关MAT的具体概述
http://www.importnew.com/2433.html
1.2.1 使用OverView观察内存使用情况
· 获取 hprof文件
点击工具栏上的 按钮,将内存信息保存成文件。 如果是用 MAT Eclipse 插件获取的 Dump文件,则不需要经过转换,Adt会自动进行转换然后打开。
刚刚开始启动程序,监听下堆大小的情况,如下图所示:
点击按钮,查看内存使用分配情况
以上是程序没有执行任何操作时,堆的内存使用情况。
下面是代码操作示例
static class Key {
private String key;
public Key(String key) {
this.key = key;
}
}
public static long getFreeMemory() {
return Runtime.getRuntime().freeMemory() / (1024 * 1024);
}
public void TestStringAndList(){
Map<Key, String> map = new HashMap<Key, String>(1000);
int counter = 0;
while (true)
{
map.put(new Key("how do you do dummyKey"), "value");
counter++;
if (counter % 1000 == 0) {
System.out.println("map size: " + map.size());
System.out.println("Free memory after count " + counter
+ " is " + getFreeMemory() + "MB");
}
}
}
代码中反复执行向map中添加数据,检测堆的内存使用情况,如下
没有执行任何操作之前:
反复执行示例代码操作之后:
两个比较
Heap Size 堆的大小,反复执行代码操作之后,内存一直增加。
Allocated 堆中已分配的大小,没有随资源回收而变小。
使用MAT工具进行分析hprof文件
启动程序没有执行任何操作之前:
反复执行示例代码操作之后:
两个图进行比较,这样很快就可以找出是那个位置出现了内存泄露,然后我们就直接分析内存增大的模块的详细信息。
下面我们拿(d)和(e)进行分析找出内存泄露的位置
(d)-->对应Problem Suspect4 直接点开detail显示内存泄露的详细,我们只要看有小圆点的地方,如下:
点击小圆点的内存地址,会弹出一个菜单,选择“List Objects”>with incoming refs 将列出该类的实例:
选中 24 200这一行,右键Path to GC Roots-->exclue all phantom/weak/soft etc. reference :
从上图中我们可以看出String在使用的时候,出现OutOfMemeryError,从这个地方我们可以找出是代码中使用String字符串造成内存增大,导致内存泄露。
(d)-->对应Problem Suspect5 直接点开detail显示内存泄露的详细,我们只要看有小圆点的地方,如下:
点击小圆点的内存地址,会弹出一个菜单,选择“List Objects”>with incoming refs 将列出该类的实例:
选中11496 11496或者29256 29256其中某一行,右键Path to GC Roots-->exclue all phantom/weak/soft etc. reference :
我们大致可以找到原因,使用自定义的静态类Key不正确。
1.2.2 通过Histogram观察内存使用情况
直方图(Histogram)
MAT最有用的工具之一,它可以列出任意一个类的实例数。查找内存泄露或者其他内存方面问题是,首先看看最有可能出问题的类,这个类有多少个实例是个比较好的选择。它支持使用正则表达式来查找某个特定的类,还可以计算出该类所有对象的保留堆最小值或者精确值。
1.计算保留堆大小
a) 计算保留堆最小值(Calculate Minimum Retained Size) –计算保留堆最小值,并显示在表格中.
b) 计算保留堆精确值(Calculate Precise Retained Size) – 计算保留堆精确值(这个过程需要一点时间) 并且显示在表格中.
2.正则表达式(Regex pattern) – 让用户查询某个特定的对象类
我们可以选择堆大小较大的几个实例数进行内存泄露的检查分析,比如查看type[]
选中byte[]行,右键选择“List Objects”>with incoming refs 将列出该类的实例:
图中有小圆点的地方,代表这个位置有内存泄露,我们可以将其展开进行更加详细的查看,选择mBitmap进行查看,直接右键
Path to GC Roots-->exclue all phantom/weak/soft etc. reference :
在这里我们可以根据图片进一步分析使用什么导致内存泄露。
下面总结一下其他情况造成内存泄露
1.单例模式的使用
2.非静态内部类创建静态实例造成的内存泄漏
3.Handler造成的内存泄漏
4.线程造成的内存泄漏
5.资源未关闭造成的内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
具体代码实例看这里:
http://android.jobbole.com/82198/