防止android内存泄露的机制方式

Android内存泄漏是一个经常要遇到的问题,程序在内存泄漏的时候很容易导致OOM的发生。那么如何查找内存泄漏和避免内存泄漏就是需要知晓的一个问题,首先我们需要知道一些基础知识。

内存泄露的定义

当一个对象已经不需要了,本该被回收时,而有另外一个正在使用的对象持有它的引用,从而导致了对象不能被GC回收。这种导致了本该被回收的对象不能被回收而停留在堆内存中的现象,就称之内存泄漏

对于C++来说,内存泄漏就是new出来的对象没有delete;
对于Java来说,就是new出来的Object 放在Heap上无法被GC回收;
这里写图片描述

内存泄漏带来的影响

1.应用卡顿
2.泄漏的内存影响了GC的内存分配,过多的内存泄漏会影响应用的执行效率
3.应用异常(OOM)
4.过多的内存泄漏,最终会导致 Dalvik可分配的内存越来越少,更加容易出现OOM

内存泄露和内存溢出的区别

内存泄漏(Memory Leak)
进程中某些对象已经没有使用的价值了,但是他们却还可以直接或间接地被引用到GC Root导致无法回收。当内存泄漏过多的时候,再加上应用本身占用的内存,日积月累最终就会导致内存溢出OOM
内存溢出(OOM)
当 应用的heap资源超过了Dalvik虚拟机分配的内存就会内存溢出

Java 中的内存分配

1.静态储存区:编译时就分配好,在程序整个运行期间都存在。它主要存放静态数据和常量;
2.栈区:当方法执行时,会在栈区内存中创建方法体内部的局部变量,方法结束后自动释放内存;
3.堆区:通常存放 new 出来的对象。由 Java 垃圾回收器回收。

Java的四种引用

这里写图片描述

Android开发常见的内存泄漏

(1)单例造成的内存泄漏
错误示例
当调用getInstance时,如果传入的context是Activity的context。只要这个单例没有被释放,那么这个Activity也不会被释放一直到进程退出才会释放。
这里写图片描述

解决方案
能使用Application的Context就不要使用Activity的Content,Application的生命周期伴随着整个进程的周期

错误示例
这里写图片描述

解决方案
单例的静态特性导致其生命周期同应用一样长。使用WeakReference

这里写图片描述

(2)在Java中,非静态内部类 和 匿名类 都会潜在的引用它们所属的外部类,但是,静态内部类却不会。如果这个非静态内部类实例做了一些耗时的操作,就会造成外围对象不会被回收,从而导致内存泄漏
错误示例
这里写图片描述
解决方案
将非静态内部类修改为静态内部类。(静态内部类不会隐式持有外部类)

错误示例
这里写图片描述

解决方案
将内部类变成静态内部类
1.如果有强引用Activity中的属性,则将该属性的引用方式改为弱引用;
2.在业务允许的情况下,当Activity执行onDestory时,结束这些耗时任务;
这里写图片描述

(3)Handler造成的内存泄漏
mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏
错误示例
这里写图片描述

解决方案
创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息

如果想在Handler内部去调用所在的Activity,那么可以在handler内部使用弱引用的方式去指向所在Activity.使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。

这里写图片描述

(4)线程造成的内存泄漏
异步任务和Runnable都是一个匿名内部类,因此它们对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏
错误示例
这里写图片描述
解决方案
使用 静态内部类,避免了Activity的内存资源泄漏,当然在Activity销毁时候也应该取消相应的任务AsyncTask::cancel(),避免任务在后台执行浪费资源
这里写图片描述

(5)资源未关闭造成的内存泄漏
错误示例
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏
解决方案
在Activity销毁时及时关闭或者注销

(6)使用了静态的Activity和View
错误示例
这里写图片描述
解决方案
应该及时将静态的应用 置为null,而且一般不建议将View及Activity设置为静态

(7)注册了系统的服务,但onDestory未注销 系统服务可以通过Context.getSystemService 获取,它们负责执行某些后台任务,或者为硬件访问提供接口。如果Context 对象想要在服务内部的事件发生时被通知,那就需要把自己注册到服务的监听器中。然而,这会让服务持有Activity 的引用,如果在Activity onDestory时没有释放掉引用就会内存泄漏。
错误示例
这里写图片描述

解决方案
//不需要用的时候记得移除监听sensorManager.unregisterListener(listener);

(8). 集合中对象没清理造成的内存泄漏

我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
所以要在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。

内存泄露的检测

第一步
这里写图片描述

第二步
这里写图片描述

第三步
这里写图片描述

集体分析可以看这篇文章写的比较好
http://www.jianshu.com/p/216b03c22bb8