Java 虚拟机拥有自动垃圾回收机制,Android 系统的内存垃圾也通过 GC 来自动回收,所以开发者平常不用过多的关心 Android 的内存管理,可以把更多的精力放到业务上。但为了整个系统的内存控制需要,Android 系统为每一个应用程序都设置一个硬性的 Dalvik Heap Size 最大限制阈值(这个阈值在不同的设备上会因为 RAM 的大小不同而有所差异),如果应用占用内存空间已经接近或超出这个阈值时,就会发生错误(OOM)或性能问题,所以内存的优化是尤为重要的。
首先,Java 不像 C 和 C++ 一样需要开发者自己释放内存,在 Android 上,申请和释放内存都是由系统层承担。
应用在使用过程中随着内存增加,当达到一定的条件后,GC 开始释放内存,在 GC 过程中,任何其它工作中的线程都会暂停,包括 UI 线程。一般这种释放操作增加的一点耗时并不会影响应用的使用,如果大量这样重复的操作,其它工作的时间就会受到压缩,进而影响到应用的渲染工作。
比如发生在短时间内申请大量对象,但极少会释放。一旦接近限定阈值,垃圾回收活动就会启动,即使有时内存申请很小,它们仍然会给应用程序的堆内存造成压力,还是会启动垃圾回收。如果 GC 频繁的话就会消耗很多的时间,很容易造成卡顿。
OOM 全称是 Out Of Memory,即内存溢出,是因为实际使用内存超出最大限定值而报错。造成 OOM 主要有以下原因:
除了导致 OOM 和 UI 卡顿,在应用程序后台运行时,特别是服务进程,即使通过一系列的方法提高进程的优先级,但如果内存占用过高,在系统资源紧张的情况下,仍然会被系统 kill 掉。
内存泄露总体来说就是没有用的本该释放回收的对象,GC 却不能回收,就造成了内存泄露,当泄露越来越多,可用内存空间越来越少时,问题就来了。
如果想详细了解请参考:Android 性能优化3 - 解决内存泄露。
Java 中的引用分为强引用、软引用、弱引用、虚引用,合理利用这几种引用类型可以帮助 GC 更合理地回收。
说明:自动装箱的核心就是把基础数据类型换成对应的复杂类型,不需要开发者自己转换。
目的:
// 不合法
List list = new ArrayList ()
// 合法
List list = new ArrayList ();
示例:
Integer total = 0;
for(int i = 0; i < 100; i++) {
total += i;
}
这样会消耗更多的性能,每次循环虚拟机都必须创建一个新的整数对象。
基本数据类型 int 只占4个字节,而 Integer 对象有16个字节,因此在内存和时间性能上都有额外的开销。
对于不必要的内存开销等问题应该及早发现,不要让问题积少成多。一般可以通过 TraceView 或 Memory Profiler 查看耗时,如果发现调用了大量的 integer.value,就说明发生了AutoBoxing,需要立即优化代码。
在Android系统中,有些数据是可以复用的,并且在开发过程中,系统也提供了可利用的接口或方法。比如以下三种复用的场景,通过内存复用可以有效减少应用的内存开销。
使用枚举类型(Enums)来定义常量,会使代码更易读并且更安全,但在性能上却比普通常量定义差很多。
Android系统在应用启动后,会给应用单独分配一块内存。应用的 dex code、Heap 以及运行时的内存分配都会在这块内存中。而使用枚举类型的 dex size 是普通常量定义的 dex size 的13倍以上(只是 dex code增加),同时,运行时的内存分配,一个 enum 值的声明会消耗至少20 bytes,这还没有算上其中的对象数组需要保持对 enum 值的引用。每个枚举项都会被声明成一个静态变量,并被赋值。因此,当应用程序中的代码或包含的 Lib 中大量使用 enum 时,对本身内存小的手机会带来不可忽视的影响。
枚举的最大优点是类型安全,但在 Android 平台上,枚举的内存开销是直接定义常量的三倍以上,所以 Android 的官方文档也提醒了开发者尽量避免使用枚举类型,同时提供注解的方式检查类型安全,目前提供了int型和String型两种注解方式:IntDef 和 StringDef,用来提供编译期的类型检查。
public class Test {
public static final int LEVEL1 = 1;
public static final int LEVEL2 = 2;
void main() {
aaa(LEVEL1);
}
void aaa(@TestInterface int level) { ... }
@IntDef({LEVEL1, LEVEL2})
@Retention(RetentionPolicy.SOURCE)
public @interface TestInterface { ... }
}
通过以上方法可以替代枚举类型的安全性。
LruCache 在 android.util 包下(android-support-v4 的包中提供),可以翻译为最近最少使用缓存。
它用强引用保存需要缓存的对象,它内部维护一个队列(实际上是 LinkedHashMap 内部的双向链表,不支持线程安全,LruCache对它进行封装,添加了线程安全操作)。
当其中的一个值被访问时,它被放到队列的尾部,当缓存将满时,队列头部的值(也就是最近最少被访问的)被丢弃,之后可以被垃圾回收。
由于 Android 为每个进程分配的存储空间有有限的,而图片又是占用内存空间很大的一块,如果没有合理的优化好图片,很可能由于内存的过量占用从而导致性能问题。
由于该块的内容较多,我将其单独放到一篇文章中详细说明,如果想详细了解可参考:
Android 内存优化4 - 图片优化
。
介绍几个检测内存问题的工具:
总体来说内存优化具有以下意义: