android内存回收检测,Android 内存泄露简介、典型情景及检测解决

什么是内存泄露?

Android虚拟机的垃圾回收采用的是根搜索算法。GC会从根节点(GC Roots)开始对heap进行遍历。到最后,部分没有直接或者间接引用到GC Roots的就是需要回收的垃圾,会被GC回收掉。内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。无用的对象占据着内存空间,导致不能及时回收这个对象所占用的内存。内存泄露积累超过Dalvik堆大小,就会发生OOM(OutOfMemory)。内存泄露的经典场景

非静态内部类的静态实例

由于内部类默认持有外部类的引用,而静态实例属于类。所以,当外部类被销毁时,内部类仍然持有外部类的引用,致使外部类无法被GC回收。因此造成内存泄露。

举个栗子private static Leak mLeak;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_second);

mLeak = new Leak();

}

class Leak {

}

错误栗子说明:static关键字修饰mLeak属性,将mLeak存在静态区中,而Leak为内部类,默认持有外部类的引用。当Activity销毁时,mLeak紧紧抱住Activity的大腿深情告白:“MLGB!劳资就是不放你走!”。斗不过mLeak属性的GC,自然不敢回收二手娘们Activity。因此造成内存泄露。

不正确的Handler

错误代码示例:private MyHandler mMyHandler;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_second);

mMyHandler = new MyHandler();

mMyHandler.sendMessageDelayed(new Message(), 10 * 1000);

}

class MyHandler extends Handler {

@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

}

}

正确写法如下:private MyHandler mMyHandler;

static class MyHandler extends Handler {

WeakReference mActivityWeak;

MyHandler(Activity act) {

mActivityWeak = new WeakReference(act);

}

@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

if (mActivityWeak.get() != null) {

// doSomething

}

}

}

我们知道在handler.sendMessage(msg)时,msg.target会指向handler,msg会插入MessageQueue。此为下面讲解的基础,对这部分不太熟悉的同学可以参考这篇博客。

错误之处

MyHandler为内部类,默认持有外部类的引用。当Activity销毁时,如果MessageQueue中仍有未处理的消息,那么mMyHandler示例将继续存在。而mMyHandler持有Activity的引用。故Activity无法被GC回收。

正确解析

static关键字修饰MyHandler类,使MyHandler不持有外部类的引用。使用WeakReference保证当

activity销毁后,不耽误gc回收activity占用的内存空间,同时在没被销毁前,可以引用activity。

管它正确错误都让它正确

通过上面的分析,可以得出结论:Handler造成内存泄露时,是因为MessageQueue中还有待处理的Message,那我们在Activity#onDestroy()中移除所有的消息不完事了嘛。反正Activity都销毁了,MessageQueue中的msg也就什么存在的意义了,可以移除。代码如下:@Override

protected void onDestroy() {

super.onDestroy();

// 移除所有的callback和msg

mMyHandler.removeCallbacksAndMessages(null);

}

静态变量引起内存泄露

这里以单例模式引起Context泄露为例public class Singleton {

private static Singleton instance;

private Singleton(Context context){

}

public static Singleton getInstance(Context context){

if (instance == null){

synchronized (Singleton.class){

if (instance == null){

instance = new Singleton(context);

}

}

}

return instance;

}

}

错误之处

在调用Singleton#getInstance()方法如果传入了Activity。如果instance没有释放,那么这个Activity将一直存在。因此造成内存泄露。

修正版

将new Singleton(context)改为new Singleton(context.getApplicationContext())即可,这样便和传入的Activity没撒关系了。该释放释放、该回家回家。

碎碎念当使用Cursor、File、Socket等资源时往往都使用了缓冲。在不需要的时候应该及时关闭它们,收回所占的内存空间。

Bitmap不用就recycle掉。注意调用recycle后并不意味着立马recycle,只是告诉虚拟机:小子,该干活咯!

ListView一定要使用ConvertView和ViewHolder

BraodcastReceiver注册完事,不用时也要反注册内存泄露的检测

Heap工具打开DDMS视图

选中Devices下某个具体的应用程序

选中Devices下第二个小绿点Update Heap

不断运行程序并点击Cause GC

关注data Object行、Toal Size列

耍你的APP去吧,如果发现Toal Size越来越大,很可能有内存泄露的发生

MAT(Memory Analyzer Tool)工具

导出.hprof文件

打开DDMS视图

选中Devices下某个具体的应用程序

选中Devices下第二个小绿点Update Heap

点击Cause GC

点击Dump HPROF file

切换到MAT页卡,默认如下图所示

76223a7f9272e15a20c52388ff94bca4.png

最显眼的就是饼图了,里面列出了每种类型的数据所占大小。和红色箭头所指的Dominator有的一拼,然而这并没有什么卵用。我们的重点在Histogram。没撒说的,点击它。默认图如下

6b303ceb9e4109fbbfb5622cb7e1b6be.png

默认是按Class排序,第一行支持正则表达式。为了查看方便,下面我们会以Group by package的形式分组。正确的打开方式应该是这个样子的。

76579e4360490153b3a932e9d8eefe1c.png

内存泄露Demo

这里以非静态内部类的静态实例为例,Demo只有两个Activity,MainActivity中只有一个按钮,点击跳转到SecondActivity。public class SecondActivity extends Activity {

private static Leak mLeak;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_second);

mLeak = new Leak();

}

class Leak {

}

}

查找内存泄露

启动APP,点击进入SecondActivity,然后按back键返回到MainActivity。打开.hprof文件。查找我们的包名com.dyk.memoryleak。

4c5fc808f4c190fda354ad7283f91e81.png

可以看到,虽然我们结束了SecondActivity,但是SecondActivity仍然存在,内存泄露无疑。

1.右键SecondActivity,选择List Objects—→with incoming references

4299ba8e10733d011c429939137951b5.png

结果如下图:

a9a68dfad506c562f253e64ca7ea5a85.png

2.右键com.dyk.memoryleak.SecondActivity,选择Path to GC—→with all references

0221a0d9832e10453e08bb2279051727.png

结果如下图:

db3a7c0667d7a8557edce573d87051fb.png

可以看到是因为mLeak属性的引用导致SecondActivity无法回收。既然找到了内存泄露的原因,通过上文的介绍,相信改起来难度应该不是很大的。

3.再次进入SecondActivity。由于上次创建的SecondActivity还没有被回收,可以预期到此时应该存在两个SecondActivity实例。

faafc9b335f30621608766b399da65c3.png

关于内存泄露的内容暂时到此为止了。MAT更多的功能,请自行查找学习。感谢耐心阅读到最后~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值