Android 运行时 (ART) 和 Dalvik 虚拟机使用分页和内存映射 (mmapping) 来管理内存。这意味着应用修改的任何内存(无论是通过分配新对象还是触摸映射页面)都将驻留在 RAM 中,并且无法被分页出。从应用释放内存的唯一方法是释放应用持有的对象引用,使内存可供垃圾回收器使用。只有一个例外:任何未修改地映射到内存中的文件(例如代码)可以在系统需要使用该内存时被分页出 RAM。
本页面介绍了 Android 如何管理应用进程和内存分配。如需了解如何在应用中更高效地管理内存,请参阅管理应用内存。
垃圾回收
受管理内存环境(例如 ART 或 Dalvik 虚拟机)会跟踪每个内存分配。一旦它确定程序不再使用某块内存,就会将其释放回堆中,无需程序员的任何干预。在受管理内存环境中回收未使用内存的机制称为垃圾回收。垃圾回收有两个目标:查找程序中未来无法访问的数据对象;以及回收这些对象使用的资源。
Android 的内存堆是分代的,这意味着它根据所分配对象的预期生命周期和大小,跟踪不同分配桶。例如,最近分配的对象属于新生代。当一个对象保持活跃足够长时间时,它可以被提升到老年代,然后是永久代。
每个堆代都有其自己的专用内存上限,用于限制对象可以占用的内存量。每当一代开始填满时,系统就会执行垃圾回收事件,以尝试释放内存。垃圾回收的持续时间取决于它正在收集的对象代以及每个代中有多少活动对象。
尽管垃圾回收速度很快,但它仍然会影响您的应用性能。通常,您无法在代码中控制垃圾回收事件何时发生。系统有一套运行中的标准来决定何时执行垃圾回收。当满足这些标准时,系统会停止执行进程并开始垃圾回收。如果垃圾回收发生在动画等密集处理循环或音乐播放期间,可能会增加处理时间。这种增加可能会使您的应用中的代码执行超出建议的 16 毫秒阈值,从而影响高效流畅的帧渲染。
此外,您的代码流可能会执行某些类型的工作,这些工作会强制垃圾回收事件更频繁地发生或使其持续时间比正常更长。例如,如果您在 alpha 混合动画的每一帧中,在 for 循环的最内层分配多个对象,您可能会用大量对象污染您的内存堆。在这种情况下,垃圾回收器会执行多个垃圾回收事件,并可能降低您的应用性能。
有关垃圾回收的更多一般信息,请参阅垃圾回收。
共享内存
为了将所需的一切都放入 RAM 中,Android 尝试在进程之间共享 RAM 页面。它可以通过以下方式实现:
- 每个应用进程都是从一个名为 Zygote 的现有进程派生出来的。Zygote 进程在系统启动时开始,并加载通用的框架代码和资源(例如 Activity 主题)。要启动新的应用进程,系统会派生 Zygote 进程,然后在新的进程中加载并运行应用代码。这种方法允许为框架代码和资源分配的大部分 RAM 页面在所有应用进程之间共享。
- 大多数静态数据都被 mmapped 到进程中。这种技术允许数据在进程之间共享,并且还允许在需要时将其分页出。静态数据示例包括:Dalvik 代码(通过将其放入预链接的
.odex
文件中进行直接 mmapping)、应用资源(通过将资源表设计为可进行 mmapping 的结构并通过对齐 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)及更高版本上可用,并为您的进程提供详细的内存警报。