什么是内存泄漏?
内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
很多人会把内存泄漏和内存溢出混淆,其实两者并不是同一个概念,但是两者却有非常重要的联系,简单来说大量的内存泄漏就会导致内存溢出。下面贴出内存溢出的概念。
内存溢出:程序向系统申请的内存空间超出了系统能给的。
比如内存只能分配一个int类型,我却要塞给他一个long类型,系统就出现oom。简单来说就是房子就那么大,但是越来越多的人进来,直到房子已经装不下这么多人,人还在往房子里面走,就会导致房子越来越拥挤,直到将房子挤爆。
我们理解了内存泄漏和内存溢出的区别之后。那么我们就来看看导致我们程序内存泄漏的根本原因是什么。
暗中观察一番之后,我们来看内存泄漏出现的根本原因。首先我们都知道java是有垃圾回收机制的。也就是我们常说的gc。gc是可以自动清除堆中我们不再使用的对象的。当然了,在java中对象是通过引用来使用的。但是如果再也没有引用指向对象的话,那么这个对象就无从处理,无从调用。在java中我们称这种对象为不可到达对象。简单来说,此对象在内存中的申请的空间我们无法回收,有对象的强引用,且没有及时释放,进而造成内存单元一直被占用,浪费空间,就可能造成内存溢出!
下面我们总结一下安卓内存泄漏出现的原因,常见内存泄漏的汇总。
1.非静态内部类或者匿名内部类隐式持有外部类对象。简单来说当非静态内部类的对象的生命周期比外部类对象生命周期长,就会引起内存泄漏。安卓比较典型场景就是使用handler.
也就是说当handler正在处理消息的时候,用户退出activity,但是这个时候handler还在处理消息。导致activity无法被回收。也就会发生内存泄漏。
解决办法:
1)将handler使用static修饰
2)handler通过弱引用的方式持有activity
3)在activity的ondestory生命周期中将handler中的消息置空
2.单例模式也会引起内存泄漏。我们使用单例模式是希望全局只有一个静态变量,如果我们传入了上下文的话,activity是间接继承上下文的。所以这个时候我们要是将activity退出,应该是回收activity的,但是单例模式还持有着它的引用,导致activity回收失败,造成内存泄漏。
解决办法:
1)不管外面传入什么上下文,我们单例模式里面都给它转化为application的context,这样单例模式的生命周期就和应用一样长,避免了内存泄漏。
3.mvp框架引起内存泄漏。Mvp框架优点很多,包括高度解耦,代码复读性强等等,但是它也有缺点。缺点之一就是容易造成内存泄漏。Presenter层持有着view的接口对象,model也很有可能拥有者presenter实例。所以当activity销毁的时候,model还在获取着数据。Presenter也就一直持有着view对象。这条gc链不间断,activity就无法正常的回收。
解决办法:
1)在actity的ondestory方法中利用presenter层进行资源释放,解除和view层的绑定,并且取消model层的网络请求。最后置空presenter层。
2)将presenter层转化为弱引用去引用view对象
4.RxJava也会引起内存泄漏。内存泄漏产生的根本原因,当一个对象处于可以被回收状态时,却因为该对象被其他暂时不可被回收的对象持有引用,而导致不能被回收,如此一来,该对象所占用的内存被回收以作他用,这部分内存就算是被泄露掉了。简单来说,就是该丢掉的垃圾还占着有用的空间没有被及时丢掉。
解决办法:
1)使用取消订阅管理器,compositeSubscription.让订阅管理器统一管理持有所有请求,统一取消。
2)使用Rxlifecycle第三方库,完成发布事件与当前组件进行绑定,实现生命周期同步,组件生命周期结束后,自动取消订阅。
3) 自己取消订阅,调用unsubscribe()方法
5.timer和timertask(属性动画)引起的内存泄漏,因为我们通常会用来做一些计时操作或者循环操作,如果忘记销毁变量的话,那么timer或者timertask可能会一直持有着activity或者其他变量,造成内存泄漏。属性动画和上述的问题是一样的,所以在这里就集中地说了。
解决办法:
1)在适当的时机调用cancel()方法(比如在activity的ondestory方法里面调用cancel方法)
6.关于webview的内存泄漏,是因为webview加载网页后长期占用内存而不能释放,也就是说webview持有着acitivity变量,导致占用的内存始终无法释放,就算是调用了webview.ondestory()也不能解决问题。
解决方法:
1)当然了,也有最终的解决方案。在webview销毁之前需要先从父容器中将webview移除。然后在调用webview的销毁方法。
Android中检查内存泄漏的方法有很多种,我们这里就介绍平时我们最常用的方法。
1.利用Android Studio自带工具进行内存泄漏的检测。首先我们先打开Android Studio的控制台(logcat),然后找到monitors,打开。我们就可以看到下面这张图这样。
当我们连接模拟器运行项目的时候,我们就可以通过自带工具看到我们的内存使用情况,当然还有其他的一些功能,(cpu的消耗情况,网络测速,Gpu的绘制情况)。我们都可以在这里看到。如果发生内存泄漏或者内存溢出的话我们就可以发现,但是这种方法不全面,必须要我们关注它的内存消耗状况。不够方便,我们需要的是如果有内存泄漏的话,能够第一时间的通知我们去解决,并且将发生内存泄漏的位置告诉我们。如果这样的话,这种方式就不能满足我们的需求了。这个时候我们就需要另外一种方法了。
2.利用Leakcanary来检查我们项目中出现的内存泄漏。
leakcanary是square公司出的一个第三方检查内存泄漏的工具,在这个工具出现之前,square公司的技术人员也被内存泄漏的问题困扰了很久,当时他们想要利用一种思路,一种方法,彻底解决内存泄漏的问题。但是后来失败了。他们发现他们距离解决问题的方向更遥远了。后来及时的调整思路,这才有了leakcanary。所以说并不是大公司的技术人员不会被这些问题困扰,人和人都是一样的。区分的只是人与人的耐心程度。
下面来介绍一下这个工具具体是怎么使用的。因为是第三方工具,所以我们需要导入两个依赖。一个是debug,一个是release
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
通常来说,我们需要使用这个工具能够检测整个项目中的内存泄漏问题,所以我还是建议抽取一个app类,在这个类中的oncreate方法中获得检测对象RefWatcher。
refWatcher= LeakCanary.install(this);
然后通过application类传递出去。具体方法如下:
public static RefWatcher getRegwatcher(Context context){
MyApp myApp = (MyApp) context.getApplicationContext();
return myApp.refWatcher;
}
然后我们就可以使用了,通过两行代码的调用,我们就可以实现实时的检测我们项目中是否出现了内存泄漏。如果我们想要在MainActivity中检测内存泄漏,我们应该具体怎么写呢?具体代码如下:
RefWatcher regwatcher = MyApp.getRegwatcher(this.getApplicationContext());
regwatcher.watch(this);
通过获得到检测对象RefWatcher,将我们需要检测的对象传递给它的watch()方法就可以了。
当然还有其他检测方法,比如MAT等等,具体使用什么方法还是要看个人本身或者项目中的实际需求,不可盲目使用。
到这里关于Android的内存泄漏常见的原因和检测方法就讲述结束了,关于内存泄漏其实还有很多我们还未了解到的知识,这些书本是不会交给我们的,我们需要实际的去体验,在项目中碰到,我们才能够对内存泄漏有更精进的了解,当然如果项目中没有任何内存泄漏,那肯定是天大的好事。以上我说的如果不对的地方,欢迎各位提出宝贵的意见,一起交流,共同进步。