Android 关于内存泄露,你真的知道吗?

前言

内存管理的目的就是让我们在开发过程中有效避免我们的应用程序出现内存泄露的问题。内存泄露相信大家都不陌生,我们可以这样理解:「没有用的对象无法回收的现象就是内存泄露」。

如果程序发生了内存泄露,则会带来以下这些问题

应用可用的内存减少,增加了堆内存的压力
降低了应用的性能,比如会触发更频繁的 GC
严重的时候可能会导致内存溢出错误,即 OOM Error
OOM 发生在,当我们尝试进行创建对象,但是堆内存无法通过 GC 释放足够的空间,堆内存也无法再继续增长,从而完成对象创建请求的时候,OOM 发生很有可能是内存泄露导致的,但并非所有的 OOM 都是由内存泄露引起的,内存泄露也并不一定引起 OOM。

一、基础准备

如果真的想比较清楚的了解内存泄露的话,对于 Java 的内存管理以及引用类型有一个清晰的认识是必不可少的。

理解 Java 的内存管理能让我们更深一层地了解 Java 虚拟机是怎样使用内存的,一旦出现内存泄露,我们也能更加从容地排查问题。

了解 Java 的引用类型,能让我们更加理解内存泄露出现的原因,以及常见的解决方法。

二、Android 中内存泄露的常见场景 & 解决方案

1、单例造成的内存泄露
单例模式是非常常用的设计模式,使用单例模式的类,只会产生一个对象,这个对象看起来像是一直占用着内存,但这并不意味着就是浪费了内存,内存本来就是拿来装东西的,只要这个对象一直都被高效的利用就不能叫做泄露。

但是过多的单例会让内存占用过多,而且单例模式由于其 静态特性,其生命周期 = 应用程序的生命周期,不正确地使用单例模式也会造成内存泄露。
举个例子:
在这里插入图片描述

上面是一个比较简单的单例模式用法,需要外部传入一个 Context 来获取该类的实例,如果此时传入的 Context 是 Activity 的话,此时单例就有持有该 Activity 的强引用(直到整个应用生命周期结束)。这样的话,即使该 Activity 退出,该 Activity 的内存也不会被回收,这样就造成了内存泄露,特别是一些比较大的 Activity,甚至还会导致 OOM(Out Of Memory)。
解决方法:单例模式引用的对象的生命周期 = 应用生命周期
在这里插入图片描述

可以看到在 SingleInstanceTest 的构造函数中,将 context.getApplicationContext() 赋值给 mContext,此时单例引用的对象是 Application,而 Application 的生命周期本来就跟应用程序是一样的,也就不存在内存泄露。

这里再拓展一点,很多时候我们在需要用到 Activity 或者 Context 的地方,会直接将 Activity 的实例作为参数传给对应的类,就像这样:
在这里插入图片描述

这种情况如果不注意的话,很容易就会造成内存泄露,比较好的写法是使用弱引用(WeakReference)来进行改进。
在这里插入图片描述

被弱引用关联的对象只能存活到下一次垃圾回收之前,也就是说即使 Sample 持有 Activity 的引用,但由于 GC 会帮我们回收相关的引用,被销毁的 Activity 也会被回收内存,这样我们就不用担心会发生内存泄露了。

2、非静态内部类 / 匿名类

我们先来看看非静态内部类(non static inner class)和 静态内部类(static inner class)之间的区别。
在这里插入图片描述

可以看到非静态内部类自动获得外部类的强引用,而且它的生命周期甚至比外部类更长,这便埋下了内存泄露的隐患。如果一个 Activity 的非静态内部类的生命周期比 Activity 更长,那么 Activity 的内存便无法被回收,也就是发生了内存泄露,而且还有可能发生难以预防的空指针问题。

举个例子:
在这里插入图片描述

可以看到我们在 Activity 中继承 AsyncTask 自定义了一个非静态内部类,在 doInbackground() 方法中做了耗时的操作,然后在 onCreate() 中启动 MyAsyncTask。如果在耗时操作结束之前,Activity 被销毁了,这时候因为 MyAsyncTask 持有 Activity 的强引用,便会导致 Activity 的内存无法被回收,这时候便会产生内存泄露。
解决方法:将 MyAsyncTask 变成非静态内部类
在这里插入图片描述

这时候 MyAsyncTask 不再持有 Activity 的强引用,即使 AsyncTask 的耗时操作还在继续,Activity 的内存也能顺利地被回收。
匿名类和非静态内部类最大的共同点就是 都持有外部类的引用,因此,匿名类造成内存泄露的原因也跟静态内部类基本是一样的,下面举个几个比较常见的例子:
在这里插入图片描述

上面举出了两个比较常见的例子
new 出一个匿名的 Thread,进行耗时的操作,如果 MainActivity 被销毁而 Thread 中的耗时操作没有结束的话,便会产生内存泄露
new 出一个匿名的 Handler,这里我采用了 sendMessageDelayed() 方法来发送消息,这时如果 MainActivity 被销毁,而 Handler 里面的消息还没发送完毕的话,Activity 的内存也不会被回收
解决方法:
继承 Thread 实现静态内部类
继承 Handler 实现静态内部类,以及在 Activity 的 onDestroy() 方法中,移除所有的消息 mHandler.removeCallbacksAndMessages(null);

3、集合类

集合类添加元素后,仍引用着集合元素对象,导致该集合中的元素对象无法被回收,从而导致内存泄露,举个例子:
在这里插入图片描述

在这个例子中,循环多次将 new 出来的对象放入一个静态的集合中,因为静态变量的生命周期和应用程序一致,而且他们所引用的对象 Object 也不能释放,这样便造成了内存泄露。
解决方法:在集合元素使用之后从集合中删除,等所有元素都使用完之后,将集合置空。
在这里插入图片描述

4、其他的情况

除了上述 3 种常见情况外,还有其他的一些情况
1、需要手动关闭的对象没有关闭
网络、文件等流忘记关闭
手动注册广播时,退出时忘记 unregisterReceiver()
Service 执行完后忘记 stopSelf()
EventBus 等观察者模式的框架忘记手动解除注册

2、static 关键字修饰的成员变量

3、ListView 的 Item 泄露

三、利用工具进行内存泄露的排查

除了必须了解常见的内存泄露场景以及相应的解决方法之外,掌握一些好用的工具,能让我们更有效率地解决内存泄露的问题。
1、Android Lint
Lint 是 Android Studio 提供的 代码扫描分析工具,它可以帮助我们发现代码机构 / 质量问题,同时提供一些解决方案,检测内存泄露当然也不在话下,使用也是非常的简单。
2、leakcanary
LeakCanary 是 Square 公司开源的「Android 和 Java 的内存泄漏检测库」,Square 出品,必属精品,功能很强大,使用也很简单。建议直接看 Github 上的说明:leakcanary。
干货未完待续…

有专业问题或者需要更多干货可以进群找柯南老师
链接:https://jq.qq.com/?_wv=1027&k=6W0g8p3E
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值