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