内存优化-Android内存管理机制

1 内存管理机制

Android应用都是在Android的虚拟机上运行,应用程序的内存分配与垃圾回收都是由虚拟机完成的。在Android系统中,虚拟机有两种运行模式,Dalvik和ART。下面学习Android内存管理机制,了解系统如何分类和回收内存。

2 Java对象生命周期

在 Android 应用程序中,大部分是 Java 程序,而 Java 对象被创建前的 Java 类字节码(编译后的.class 文件)需要从文件系统加载到虚拟机。Java 对象在虚拟机上运行有 7 个阶段,也就是对象的生命周期

Created —> InUse —> Invisible —> Unreachable —> Collected —> Finalized —> Deallocated

  • 创建阶段(Created)

创建 Java 对象阶段的具体步骤如下:

  1. 为对象分配存储空间。
  2. 构造对象。
  3. 从超类到子类对 static 成员进行初始化,类的 static 成员的初始化在ClassLoader 加载该类时进行。
  4. 超类成员变量按顺序初始化,递归调用超类的构造方法。
  5. 子类成员变量按顺序初始化,一旦对象被创建,子类构造方法就调用该对象并为某些变量赋值,完成后这个对象的状态就切换到了应用阶段。
  • 应用阶段(InUse)

对象至少被一个强引用(Strong Reference)持有,除非在系统中显式地使用了软引用(Soft Reference)、弱引用(Weak Reference)或虚引用(Phantom Reference)。

  • 不可见阶段(Invisible)

处于不可见阶段的对象在虚拟机的对象引用根集合中再也找不到直接或间接地强引用,这些对象一般是所有线程栈中的临时变量。所有已经装载的静态变量或者是对本地代码接口的引用。
当一个对象处于不可见阶段时,说明程序本身不再持有该对象的任何强引用,虽然该对象仍然是存在的。简单的例子就是程序的执行已经超出了该对象的作用域了。该对象仍可能被虚拟机下的某些已装载的静态变量线程或 JNI 等强引用持有,这些特殊的强引用称为“GCRoot”。存在这些 GC root 会导致对象的内存泄漏,无法被回收。

  • 不可达阶段(Unreachable)

对象处于不可达阶段是指该对象不再被任何强引用持有,回收器发现该对象已经不可达。

  • 收集阶段(Collected )

当垃圾回收器发现该对象已经处于“不可达阶段”并且垃圾回收器已经对该对象的内存空间重新分配做好准备时,对象进入“收集阶段”。如果该对象已经重写了 finalize()方法,则执行该方法的操作。

  • 终结阶段(Finalized )

当对象执行完 finalize()方法后仍然处于不可达状态时,该对象进入终结阶段。在该阶段,等待垃圾回收器回收该对象空间。

  • 对象空间重新分配阶段(Deallocated )

若垃圾回收器对该对象占用的内存空间进行回收或者再分配,则该对象彻底消失,这个阶段称为“对象空间重新分配阶段”。

3 内存分配

在了解 Android 内存模型前,先要了解堆的概念,在 Android 系统中,堆实际上就是一块匿名共享内存。Android 虚拟机并没有直接管理这块匿名共享内存,而是把它封装成一个mSpace,由底层 C 库来管理,并且仍然使用 libc 提供的函数 malloc 和 free 来分配和释放内存,当然也是在一个 mSpace 进行的。Android 应用的进程都是从一个叫作 Zygote 的进程衍生出来的(调用 fork 方法)。系统启动 Zygote 进程后,为了启动一个新的应用程序进程,系统会衍生 Zygote 进程生成一个新的进程,然后在新的进程中加载并运行应用程序的代码。这就使得大多数的 RAM pages 被用来分配给 framework 的代码,同时促使 RAM 资源能够在应用的所有进程之间共享。

大多数静态数据被映射到一个进程中。这不仅使得同样的数据能够在进程间共享,而且使得它能够在需要时能共享使用。常见的静态数据包括 Dalvik Code、app resources、so 文件等。

在大多数情况下,Android 通过显式分配共享内存区域(如 ashmem 或者 gralloc)来实现动态 RAM 区域能够在不同进程之间共享的机制。例如,Window Surface 在 App 与 ScreenCompositor之间使用共享的内存,Cursor Buffers在Content Provider与Clients之间共享内存。为了整个系统的内存控制需要Android系统为每一个应用程序都设置一个硬性的DalvikHeap Size 最大限制阈值,这个阈值在不同的设备上会因为 RAM 大小不同而有所差异。如果应用占用内存空间已经接近这个阈值时,再尝试分配内存的话,就很容易引发Out Of MemoryError 错误。Android 系统的内存堆被划分为不同的区块,根据对数据配置的类型分配不同的区域内存,垃圾回收时,也会根据这些配置执行不同的回收处理过程,并且每个区块都有指定的单位大小。

前面提到过,在 Android Runtime(Android 运行环境)有两种虚拟机,Dalvik 和 ART,它们分配的内存区域块是不同的,如图

在这里插入图片描述

虚拟机对整个堆又划分出了不同的空间,这些空间用来存储不同的数据。
不论是在 Dalvik 虚拟机模式,还是在 ART 虚拟机模式,运行时堆都划分为三个空间:LinearAlloc、Zygote Space(Zygote Heap)和 Allocation Space(Active Heap)。Dalvik 中的LinearAlloc 是一个线性内存空间,并且是一个只读区域,主要用来存储虚拟机中的类,因为类加载后只需要读的属性,并且不会改变它。把这些只读属性,以及在整个进程的生命周期都不能结束的永久数据放到线性分配器中管理,能很好地减少堆混乱和垃圾扫描,加快内存管理性能。

ART 虚拟机 Zygote Space 和 Allocation Space 与 Dalvik 虚拟机中的 Zygote Space 和Allocation Space 一样。Zygote Space 在 Zygote 进程和应用程序进程之间共享,Allocation Space则是每个进程独占。Android 系统的第一个虚拟机由 Zyg ote 进程创建并且只有一个 ZygoteSpace。但是当 Zygote 进程在 fork 第一个应用程序进程之前,会将已经使用的那部分堆内存划分为一部分,还没有使用的堆内存划分为另外一部分,也就是 Allocation Space。但无论是应用程序进程,还是 Zygote 进程,当它们需要分配对象时,都是在 Allocation Space 堆上进行。

在 ART 运行时,堆除了 Zygote Space、Allocation Space,又多了两个空间,即 Image Space和 Large Object Space。其中 Image Space 用来存放一些预加载类,和 Dalvik 中的 Linear Alloc类似,而 Large Object Space 是一些离散地址的集合,用来分配一些大对象,这样可以提高GC 的管理效率和整体性能。其中 Image Space 和 Zygote Space 在 Zygote 进程和应用程序进程之间共享,而 Allocation Space 是每个进程都独立拥有一份。

发布了119 篇原创文章 · 获赞 28 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/ldxlz224/article/details/100357664