-
很多开发者都知道,在面试的时候会经常被问到内存泄露和内存溢出的问题。
1.内存溢出(Out Of Memory,简称 OOM),通俗理解就是内存不够,即内存占用超出内存的空间大小。
2.内存泄漏(Memory Leak),简单理解就是内存使用完毕之后本该垃圾回收却未被回收。
-
在正式了解内存泄露之前,首先来简单回顾一下 Java 内存分配策略。
Java 程序运行时的内存分配策略有三种,分别是静态分配、栈式分配、堆式分配,对应的主要内存空间分别是静态存储区(也称方法区)、栈区、堆区。
1.静态存储区(方法区)
主要存放静态数据、全局 static 数据和常量。这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在。
2.栈区
当方法被执行时,方法体内的局部变量都在栈上创建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。因为栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
3.堆区
又称动态内存分配,通常就是指在程序运行时直接 new 出来的内存。这部分内存在不使用时将会由 Java 垃圾回收器来负责回收。
-
在 Java 中,内存的分配是由程序完成的,而内存的释放是由 GC 完成的,这种方式大大简化了程序员的工作,但同时却加重了 JVM 的工作,这也是 Java 程序运行速度较慢的原因之一。GC 为了能够正确释放对象,必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,监视对象的状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。
END
内存泄露
-
Java 有了垃圾回收功能,程序员无需手动管理内存分配,减少了段错误导致的闪退,也减少了内存泄漏导致的堆空间膨胀,让编写的代码更加安全。但是 Java 中依然有可能发生内存泄露,而 Android 主要使用 Java 作为开发语言,在开发过程中很可能一个很小的错误都会引起内存的泄露。有内存泄露存在时,APP 就会浪费大量的内存,就会由于内存不够而频繁进行垃圾回收,大家知道垃圾回收是非常耗时的操作,这样就会导致 APP 的严重卡顿。在最坏的时候,甚至由于内存耗尽导致OutOfMemery,最终程序异常退出。
-
内存泄露是一个令很多开发者头疼的问题,那么我们今天就来学习一下 Activity 有关的内存泄露问题。
在 Android 中,泄露 Context 对象的问题尤其严重,特别像 Activity 这样的 Context 对象会引用大量很占用内存的对象,如果 Context 对象发生了内存泄漏,那它所引用的所有对象都被泄漏了。Activity 是非常重量级的对象,所以我们应该极力避免妨碍系统对其进行回收,然而实际情况是有多种方式会无意间就泄露了Activity 对象。
END
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泄漏。
END