分享

Android常见内存泄露,学会这六招大大优化APP性能

 _王文波 2017-03-07

很多开发者都知道,在面试的时候会经常被问到内存泄露和内存溢出的问题。

内存溢出(Out Of Memory,简称 OOM),通俗理解就是内存不够,即内存占用超出内存的空间大小。

内存泄漏(Memory Leak),简单理解就是内存使用完毕之后本该垃圾回收却未被回收。

打开应用保存高清大图

在正式了解内存泄露之前,首先来简单回顾一下 Java 内存分配策略。

Java 程序运行时的内存分配策略有三种,分别是静态分配、栈式分配、堆式分配,对应的主要内存空间分别是静态存储区(也称方法区)、栈区、堆区。

静态存储区(方法区)

主要存放静态数据、全局 static 数据和常量。这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在。

栈区

当方法被执行时,方法体内的局部变量都在栈上创建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。因为栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

堆区

又称动态内存分配,通常就是指在程序运行时直接 new 出来的内存。这部分内存在不使用时将会由 Java 垃圾回收器来负责回收。

打开应用保存高清大图

在 Java 中,内存的分配是由程序完成的,而内存的释放是由 GC 完成的,这种方式大大简化了程序员的工作,但同时却加重了 JVM 的工作,这也是 Java 程序运行速度较慢的原因之一。GC 为了能够正确释放对象,必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,监视对象的状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。

Java 有了垃圾回收功能,程序员无需手动管理内存分配,减少了段错误导致的闪退,也减少了内存泄漏导致的堆空间膨胀,让编写的代码更加安全。但是 Java 中依然有可能发生内存泄露,而 Android 主要使用 Java 作为开发语言,在开发过程中很可能一个很小的错误都会引起内存的泄露。有内存泄露存在时,APP 就会浪费大量的内存,就会由于内存不够而频繁进行垃圾回收,大家知道垃圾回收是非常耗时的操作,这样就会导致 APP 的严重卡顿。在最坏的时候,甚至由于内存耗尽导致OutOfMemery,最终程序异常退出。

打开应用保存高清大图

内存泄露是一个令很多开发者头疼的问题,那么我们今天就来学习一下 Activity 有关的内存泄露问题。

Android 中,泄露 Context 对象的问题尤其严重,特别像 Activity 这样的 Context 对象会引用大量很占用内存的对象,如果 Context 对象发生了内存泄漏,那它所引用的所有对象都被泄漏了。Activity 是非常重量级的对象,所以我们应该极力避免妨碍系统对其进行回收,然而实际情况是有多种方式会无意间就泄露了Activity 对象。

1. 静态变量造成的内存泄漏

最简单的泄漏 Activity 就是在 Activity 类中定义一个 static 变量,并将其指向一个运行中的 Activity 实例。如果在 Activity 的生命周期结束之前,没有清除这个引用,那它就会泄漏。由于 Activity 的类对象是静态的,一旦加载,就会在 APP 运行时一直常驻内存,如果类对象不卸载,其静态成员就不会被垃圾回收。

2. 单例造成的内存泄漏

另一种类似的情况是对经常启动的 Activity 实现一个单例模式,让其常驻内存可以使它能够快速恢复状态。

如我们有一个创建起来非常耗时的 View,在同一个 Activity 不同的生命周期中都保持不变呢,就为它实现一个单例模式。一旦 View 被加载到界面中,它就会持有 Context 的强引用,也就是我们的 Activity 对象。

由于我们是通过一个静态成员引用了这个 View,所以我们也就引用了 Activity,因此 Activity 就发生了泄漏。所以一定不要把加载的 View 赋值给静态变量,如果你真的需要,那一定要确保在 Activity 销毁之前将其从 View 层级中移除。

3. 内部类造成的内存泄漏

我们经常在 Activity 内部定义一个内部类,这样做可以增加封装性和可读性。但是如果当我们创建了一个内部类的对象,并通过静态变量持有了 Activity 的引用,那也会可能发生 Activity 泄漏。

4. 线程造成的内存泄漏

在 Activity 内定义了一个匿名的 AsyncTask 对象,就有可能发生内存泄漏。如果 Activity 被销毁之后 AsyncTask 仍然在执行,那就会阻止垃圾回收器回收Activity 对象,进而导致内存泄漏,直到执行结束才能回收 Activity。

同样的,使用 Thread 和 TimerTask 也可能导致 Activity 泄漏。只要它们是通过匿名类创建的,尽管它们在单独的线程被执行,它们也会持有对 Activity 的强引用,进而导致内存泄漏。

5.Handler 造成的内存泄漏

定义一个匿名的 Runnable 对象并将其提交到 Handler 上也可能导致 Activity 泄漏。Runnable 对象间接地引用了定义它的 Activity 对象,而它会被提交到Handler 的 MessageQueue 中,如果它在 Activity 销毁时还没有被处理,就会导致 Activity 泄漏。

6. 资源未关闭造成的内存泄漏

如系统服务可以通过 context.getSystemService 获取,它们负责执行某些后台任务,或者为硬件访问提供接口。如果 Context 对象想要在服务内部的事件发生时被通知,那就需要把自己注册到服务的监听器中。然而,这会让服务持有 Activity 的引用,如果开发者忘记在 Activity 销毁时取消注册,也会导致 Activity泄漏。

打开应用保存高清大图

这里只是简单的分析了常见的有关 Activity 导致的内存泄露,其实导致内存泄露的地方还有很多,但是基本原理都一样。由于篇幅原因,这里不做过多介绍,以后再逐渐进行分析和学习。

虽然现在手机内存越来越大,内存泄露不会像以前由于内存过小造成 OOM。但是过量的内存泄露依然会造成内存溢出,影响用户体验,所以解决好内存泄露的问题非常重要。

今天就先分享到这里,后续将推出更多精彩内容,欢迎一起探讨学习进步。

此文章为分享达人秀——鑫鱻原创,若转载请备注出处,特此声明!

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多