Android 运行时 (ART) 和 Dalvik 虚拟机使用 分页 和 内存映射 (mmapping) 来管理内存。这意味着应用修改的任何内存(无论是分配新对象还是触碰映射的页面)都保留在 RAM 中,并且无法分页出。从应用中释放内存的唯一方法是释放应用持有的对象引用,使内存可供垃圾回收器使用。只有一个例外:任何未经修改的内存映射文件(例如代码)如果系统想要在其他地方使用该内存,都可以从 RAM 中分页出。
此页面说明 Android 如何管理应用进程和内存分配。有关如何在应用中更有效地管理内存的更多信息,请参阅 管理应用的内存。
垃圾回收
像 ART 或 Dalvik 虚拟机这样的托管内存环境会跟踪每个内存分配。一旦它确定程序不再使用某块内存,它就会将其释放回堆,而无需程序员干预。在托管内存环境中回收未使用内存的机制称为垃圾回收。垃圾回收有两个目标:查找程序中将来无法访问的数据对象;以及回收这些对象使用的资源。
Android 的内存堆是分代的,这意味着它会根据分配的对象的预期生命周期和大小跟踪不同的分配存储桶。例如,最近分配的对象属于年轻代。当对象保持活动足够长时间时,它可以提升到较老的世代,然后提升到永久代。
每个堆世代都有自己的专用上限,限制了那里对象可以占用的内存量。每当某个世代开始填满时,系统都会执行垃圾回收事件以尝试释放内存。垃圾回收的持续时间取决于它正在收集哪一代对象以及每代中有多少活动对象。
尽管垃圾回收的速度很快,但它仍然会影响应用程序的性能。通常情况下,您无法从代码中控制垃圾回收事件何时发生。系统有一套运行时的标准来确定何时执行垃圾回收。当满足这些标准时,系统会停止执行进程并开始垃圾回收。如果垃圾回收发生在密集处理循环(如动画或音乐播放)的中间,则可能会增加处理时间。这种增加可能会导致应用程序中的代码执行超过推荐的 16 毫秒阈值,从而影响帧渲染的效率和流畅性。
此外,您的代码流程可能会执行一些工作,导致垃圾回收事件发生得更频繁或持续时间更长。例如,如果您在 alpha 混合动画的每一帧中,在 for 循环的最内层分配多个对象,则可能会导致内存堆中充斥大量对象。在这种情况下,垃圾回收器会执行多次垃圾回收事件,从而降低应用程序的性能。
有关垃圾回收的更多一般信息,请参阅垃圾回收。
共享内存
为了使 Android 能够将所有需要的内容都放入 RAM 中,它会尝试跨进程共享 RAM 页面。它可以通过以下方式实现:
- 每个应用程序进程都从一个称为 Zygote 的现有进程派生而来。Zygote 进程在系统启动时启动,并加载通用的框架代码和资源(例如活动主题)。要启动新的应用程序进程,系统会派生 Zygote 进程,然后在新进程中加载并运行应用程序的代码。这种方法允许为框架代码和资源分配的大多数 RAM 页面在所有应用程序进程之间共享。
- 大多数静态数据都使用 mmap 映射到进程中。此技术允许在进程之间共享数据,并且还允许在需要时将其分页到磁盘。静态数据的示例包括:Dalvik 代码(通过将其放置在预链接的
.odex
文件中以进行直接 mmap 映射)、应用程序资源(通过设计资源表使其成为可以 mmap 映射的结构,并对齐 APK 的 zip 条目)以及传统的项目元素,例如.so
文件中的原生代码。 - 在许多地方,Android 使用显式分配的共享内存区域(使用 ashmem 或 gralloc)跨进程共享相同的动态 RAM。例如,窗口曲面在应用程序和屏幕合成器之间使用共享内存,而光标缓冲区在内容提供程序和客户端之间使用共享内存。
由于广泛使用共享内存,因此确定应用程序使用了多少内存需要格外小心。在调查 RAM 使用情况中讨论了正确确定应用程序内存使用情况的技术。
分配和回收应用程序内存
Dalvik 堆被限制在每个应用程序进程的单个虚拟内存范围内。这定义了逻辑堆大小,它可以根据需要增长,但只能增长到系统为每个应用程序定义的限制。
堆的逻辑大小与堆使用的物理内存量不同。在检查应用程序的堆时,Android 会计算一个称为比例集大小 (PSS) 的值,该值考虑了与其他进程共享的脏页面和干净页面,但仅考虑与这些 RAM 共享的应用程序数量成比例的部分。系统将此 (PSS) 总计视为您的物理内存占用量。有关 PSS 的更多信息,请参阅调查 RAM 使用情况指南。
Dalvik 堆不会压缩堆的逻辑大小,这意味着 Android 不会对堆进行碎片整理以释放空间。只有当堆末尾有未使用空间时,Android 才能缩小逻辑堆的大小。但是,系统仍然可以减少堆使用的物理内存。垃圾回收后,Dalvik 会遍历堆并查找未使用的页面,然后使用 madvise 将这些页面返回给内核。因此,大型块的配对分配和释放应该会导致回收所有(或几乎所有)使用的物理内存。但是,从小型分配中回收内存效率可能会低得多,因为用于小型分配的页面可能仍然与尚未释放的其他内容共享。
限制应用程序内存
为了维护功能正常的任务环境,Android 会为每个应用程序设置堆大小的硬性限制。确切的堆大小限制因设备而异,具体取决于设备可用的 RAM 总量。如果您的应用程序已达到堆容量并尝试分配更多内存,则可能会收到OutOfMemoryError
。
在某些情况下,您可能希望查询系统以确定当前设备上可用的确切堆空间量,例如,确定缓存中安全保存的数据量。您可以通过调用getMemoryClass()
来查询系统以获取此数字。此方法返回一个整数,指示应用程序堆可用的兆字节数。
切换应用程序
当用户在应用程序之间切换时,Android 会将非前台应用程序(即对用户不可见或未运行前台服务,例如音乐播放)保留在缓存中。例如,当用户首次启动应用程序时,会为其创建一个进程;但是,当用户离开应用程序时,该进程不会退出。系统会将该进程保留在缓存中。如果用户稍后返回到该应用程序,则系统会重用该进程,从而使应用程序切换速度更快。
如果您的应用程序有一个缓存进程并且它保留了当前不需要的资源,那么即使用户没有使用它,您的应用程序也会影响系统的整体性能。当系统资源(如内存)不足时,它会终止缓存中的进程。系统还会考虑持有最多内存的进程,并可以终止它们以释放 RAM。
注意:您的应用程序在缓存中消耗的内存越少,它不被终止并能够快速恢复的可能性就越大。但是,根据即时系统需求,缓存的进程可能会随时终止,无论其资源利用率如何。
有关不在前台运行时进程如何被缓存以及 Android 如何决定哪些进程可以被终止的更多信息,请参阅进程和线程指南。
内存压力测试
尽管内存压力问题在高端设备上不太常见,但它们仍然可能导致低 RAM 设备(例如运行 Android (Go 版) 的设备)的用户遇到问题。尝试重现这种内存压力环境非常重要,以便您可以编写检测测试以验证应用程序行为并改善低内存设备上用户的体验。
压力应用程序测试
压力应用程序测试 (stressapptest
) 是一种内存接口测试,有助于创建逼真的高负载情况,以测试应用程序的各种内存和硬件限制。通过定义时间和内存限制的功能,您可以编写检测以验证高内存情况的真实遭遇。例如,使用以下命令集将静态库推送到您的数据文件系统中,使其可执行,并运行 20 秒 990 MB 的压力测试adb push stressapptest /data/local/tmp/ adb shell chmod 777 /data/local/tmp/stressapptest adb shell /data/local/tmp/stressapptest -s 20 -M 990
stressapptest
文档。关于 stressapptest 的观察
像stressapptest
这样的工具可用于请求大于可用内存的内存分配。此类请求可能会引发各种警报,您应该从开发方面了解这些警报。由于内存可用性低而可能引发的三个主要警报包括- SIGABRT:当系统已经在内存压力下时,由于请求的分配大小大于可用内存,这会导致进程发生致命性原生崩溃。
SIGQUIT
:当您的检测测试检测到时,会生成核心内存转储并终止进程。TRIM_MEMORY_EVENTS
:这些回调在 Android 4.1(API 级别 16)及更高版本中可用,并为您的进程提供详细的内存警报。