进程间的内存分配

Android 平台基于“空闲内存就是浪费的内存”的理念运行。它始终尝试使用所有可用内存。例如,系统在应用关闭后会将其保留在内存中,以便用户可以快速切换回它们。因此,Android 设备通常运行时可用内存很少。内存管理对于在重要的系统进程和许多用户应用程序之间正确分配内存至关重要。

此页面讨论了 Android 如何为系统和用户应用程序分配内存的基本知识。它还解释了操作系统如何应对内存不足的情况。

内存类型

Android 设备包含三种不同类型的内存:RAM、zRAM 和存储。请注意,CPU 和 GPU 都访问相同的 RAM。

Types of memory

图 1. 内存类型 - RAM、zRAM 和存储

  • RAM 是最快的内存类型,但其大小通常有限。高端设备通常具有最大的 RAM 容量。

  • zRAM 是 RAM 中用于交换空间的一部分。所有内容在放入 zRAM 时都会被压缩,然后在从 zRAM 中复制出来时解压缩。这部分 RAM 的大小会随着页面移入或移出 zRAM 而增大或缩小。设备制造商可以设置最大尺寸。

  • 存储包含所有持久性数据,例如文件系统以及所有应用、库和平台的包含的对象代码。存储的容量远大于其他两种类型的内存。在 Android 上,存储不会像其他 Linux 实现那样用作交换空间,因为频繁写入会导致此内存磨损,并缩短存储介质的使用寿命。

内存页面

RAM 被分成页面。通常每个页面为 4KB 内存。

页面被认为是空闲的已使用的。空闲页面是未使用的 RAM。已使用页面是系统正在积极使用的 RAM,并分为以下类别

  • 缓存:存储中文件的支持内存(例如,代码或内存映射文件)。有两种类型的缓存内存
    • 私有:由一个进程拥有,不共享
      • 干净:存储中文件的未修改副本,可以由 kswapd 删除以增加空闲内存
      • 脏:存储中文件的已修改副本;可以由 kswapd 移动到 zRAM 或在 zRAM 中压缩以增加空闲内存
    • 共享:由多个进程使用
      • 干净:存储中文件的未修改副本,可以由 kswapd 删除以增加空闲内存
      • 脏:存储中文件的已修改副本;允许将更改写回存储中的文件以通过 kswapd 增加空闲内存,或显式使用 msync()munmap()
  • 匿名:内存由存储中的文件支持(例如,由 mmap() 分配,并设置了 MAP_ANONYMOUS 标志)
    • 脏:可以由 kswapd 移动/压缩到 zRAM 以增加空闲内存

随着系统主动管理 RAM,空闲页面和已使用页面的比例会随着时间而变化。本节中介绍的概念是管理低内存情况的关键。本文档的下一部分将更详细地解释它们。

低内存管理

Android 有两种主要的机制来处理低内存情况:内核交换守护程序和低内存杀手。

内核交换守护程序

内核交换守护程序(kswapd)是 Linux 内核的一部分,它将已使用内存转换为空闲内存。当设备上的空闲内存不足时,守护程序会变为活动状态。Linux 内核维护低和高空闲内存阈值。当空闲内存低于低阈值时,kswapd 开始回收内存。一旦空闲内存达到高阈值,kswapd 就会停止回收内存。

kswapd 可以通过删除干净页面来回收它们,因为它们由存储支持并且未被修改。如果进程尝试访问已被删除的干净页面,系统会将页面从存储复制到 RAM。此操作称为按需分页

Clean page backed by storage deleted

图 2. 由存储支持的干净页面被删除

kswapd 可以将缓存的私有脏页面和匿名脏页面移动到 zRAM,并在其中进行压缩。这样做可以释放 RAM 中的可用内存(空闲页面)。如果进程尝试访问 zRAM 中的脏页面,则该页面将解压缩并移回 RAM。如果与压缩页面关联的进程被终止,则该页面将从 zRAM 中删除。

如果空闲内存量低于某个阈值,系统将开始终止进程。

Dirty page moved to zRAM and compressed

图 3. 脏页面被移动到 zRAM 并压缩

低内存杀手

很多时候,kswapd 无法为系统释放足够的内存。在这种情况下,系统使用 onTrimMemory() 通知应用内存不足,并且它应该减少其分配。如果这还不够,内核将开始终止进程以释放内存。它使用低内存杀手 (LMK) 来执行此操作。

为了决定终止哪个进程,LMK 使用称为 oom_adj_score 的“内存不足”分数来优先考虑正在运行的进程。分数高的进程将首先被终止。后台应用将首先被终止,系统进程将最后被终止。下表列出了从高到低的 LMK 评分类别。第一行中得分最高的项目将首先被终止

Android processes, high scores at the top

图 4. Android 进程,顶部为高分,底部为低分

这些是对上表中各个类别的描述

  • 后台应用:以前运行但当前未活动的应用。LMK 将首先终止后台应用,从具有最高 oom_adj_score 的应用开始。

  • 前一个应用:最近使用的后台应用。前一个应用比后台应用具有更高的优先级(得分更低),因为它比后台应用更有可能被用户切换到。

  • 主屏幕应用:这是启动器应用。终止它会导致壁纸消失。

  • 服务:服务由应用程序启动,可能包括同步或上传到云端。

  • 可感知应用:以某种方式对用户可见的非前台应用,例如运行显示小型 UI 的搜索进程或收听音乐。

  • 前台应用:当前正在使用的应用。终止前台应用看起来像应用程序崩溃,这可能向用户表明设备出现问题。

  • 持久性(服务):这些是设备的核心服务,例如电话和 wifi。

  • 系统:系统进程。当这些进程被终止时,手机可能会显示重新启动。

  • 原生:系统使用的非常底层的进程(例如,kswapd)。

设备制造商可以更改 LMK 的行为。

计算内存占用

内核跟踪系统中的所有内存页面。

Pages used by different processes

图 5. 不同进程使用的页面

在确定应用使用了多少内存时,系统必须考虑共享页面。访问相同服务或库的应用将共享内存页面。例如,Google Play 服务和游戏应用可能共享位置服务。这使得难以确定多少内存属于服务本身以及每个应用程序。

Pages shared by two apps

图 6. 两个应用共享的页面(中间)

要确定应用程序的内存占用,可以使用以下任何指标

  • 驻留集大小 (RSS):应用使用的共享和非共享页面的数量
  • 比例集大小 (PSS):应用使用的非共享页面的数量以及共享页面的均匀分配(例如,如果三个进程共享 3MB,则每个进程在 PSS 中获得 1MB)
  • 唯一集大小 (USS):应用使用的非共享页面的数量(不包括共享页面)

当操作系统想要知道所有进程使用了多少内存时,PSS 会很有用,因为页面不会被多次计算。PSS 需要很长时间才能计算出来,因为系统需要确定哪些页面是共享的以及由多少个进程共享。RSS 不区分共享页面和非共享页面(使其计算速度更快),并且更适合跟踪内存分配的变化。

其他资源