调查 RAM 使用情况
开发 Android 应用时,请始终注意您的应用使用了多少随机存取存储器 (RAM)。尽管 Dalvik 和 ART 运行时会执行例行垃圾回收 (GC),您仍然需要了解应用何时以及在哪里分配和释放内存。为了提供稳定的用户体验,使 Android 操作系统能够在应用之间快速切换,请确保您的应用在用户不与其交互时不会消耗不必要的内存。
即使您在开发过程中遵循了管理应用的内存的所有最佳做法,您仍然可能泄漏对象或引入其他内存错误。唯一能够确定您的应用尽可能少地使用内存的方法是,利用本文介绍的工具分析应用的内存使用情况。
解读日志消息
开始调查您的应用内存使用情况的最简单切入点是运行时日志消息。有时,发生垃圾回收时,您可以在 logcat 中查看消息。
Dalvik 日志消息
在 Dalvik(而不是 ART)中,每次垃圾回收都会将以下信息打印到 logcat 中:
D/dalvikvm: <GC_Reason> <Amount_freed>, <Heap_stats>, <External_memory_stats>, <Pause_time>
示例:
D/dalvikvm( 9050): GC_CONCURRENT freed 2049K, 65% free 3571K/9991K, external 4703K/5261K, paused 2ms+2ms
-
垃圾回收原因<GC_Reason>GC_CONCURRENT
什么触发了垃圾回收以及是哪种回收。可能出现的原因包括:- GC_CONCURRENT
在您的堆开始占用内存时可以释放内存的并发垃圾回收。 - GC_FOR_MALLOC
堆已满而系统不得不停止您的应用并回收内存时,您的应用尝试分配内存而引起的垃圾回收。 - GC_HPROF_DUMP_HEAP
当您请求创建 HPROF 文件来分析堆时出现的垃圾回收。 - GC_EXPLICIT
显式垃圾回收,例如当您调用 gc() 时(您应避免调用,而应信任垃圾回收会根据需要运行)。 - GC_EXTERNAL_ALLOC
这仅适用于 API 级别 10 及更低级别(更新版本会在 Dalvik 堆中分配任何内存)。外部分配内存的垃圾回收(例如存储在原生内存或 NIO 字节缓冲区中的像素数据)。
- GC_CONCURRENT
-
释放量<Amount_freed>freed 2049K
从此次垃圾回收中回收的内存量。 -
堆统计数据<Heap_stats>65% free 3571K/9991K
堆的可用空间百分比与(活动对象数量)/(堆总大小)。 -
外部内存统计数据<External_memory_stats>external 4703K/5261K
API 级别 10 及更低级别的外部分配内存(已分配内存量)/(发生回收的限值)。 -
暂停时间<Pause_time>paused 2ms+2ms
堆越大,暂停时间越长。并发暂停时间显示了两个暂停:一个出现在回收开始时,另一个出现在回收快要完成时。
在这些日志消息积聚时,请注意堆统计数据的增大(上面示例中的 3571K/9991K 值)。如果此值继续增大,可能会出现内存泄漏。
ART 日志消息
与 Dalvik 不同,ART 不会为未明确请求的垃圾回收记录消息。只有在认为垃圾回收速度较慢时才会打印垃圾回收。更确切地说,仅在垃圾回收暂停时间超过 5ms 或垃圾回收持续时间超过 100ms 时。如果应用未处于可察觉的暂停进程状态,那么其垃圾回收不会被视为较慢。始终会记录显式垃圾回收。
ART 会在其垃圾回收日志消息中包含以下信息:
I/art: <GC_Reason> <GC_Name> <Objects_freed>(<Size_freed>) AllocSpace Objects, <Large_objects_freed>(<Large_object_size_freed>) <Heap_stats> LOS objects, <Pause_time(s)>
示例:
I/art : Explicit concurrent mark sweep GC freed 104710(7MB) AllocSpace objects, 21(416KB) LOS objects, 33% free, 25MB/38MB, paused 1.230ms total 67.216ms
- 垃圾回收原因<GC_Reason>Explicit
什么触发了垃圾回收以及是哪种回收。可能出现的原因包括:- Concurrent
不会暂停应用线程的并发垃圾回收。此垃圾回收在后台线程中运行,而且不会阻止分配。 - Alloc
您的应用在堆已满时尝试分配内存引起的垃圾回收。在这种情况下,分配线程中发生了垃圾回收。 - Explicit
由应用明确请求的垃圾回收,例如,通过调用 gc() 或 gc()。与 Dalvik 相同,在 ART 中,最佳做法是您应信任垃圾回收并避免请求显式垃圾回收(如果可能)。不建议使用显式垃圾回收,因为它们会阻止分配线程并不必要地浪费 CPU 周期。如果显式垃圾回收导致其他线程被抢占,那么它们也可能会导致卡顿(应用中出现间断、抖动或暂停)。 - NativeAlloc
原生分配(如位图或 RenderScript 分配对象)导致出现原生内存压力,进而引起的回收。 - CollectorTransition
由堆转换引起的回收;此回收由运行时切换垃圾回收引起。回收器转换包括将所有对象从空闲列表空间复制到碰撞指针空间(反之亦然)。当前,回收器转换仅在以下情况下出现:在 RAM 较小的设备上,应用将进程状态从可察觉的暂停状态变更为可察觉的非暂停状态(反之亦然)。 - HomogeneousSpaceCompact
齐性空间压缩是空闲列表空间到空闲列表空间压缩,通常在应用进入到可察觉的暂停进程状态时发生。这样做的主要原因是减少 RAM 使用量并对堆进行碎片整理。 - DisableMovingGc
这不是真正的垃圾回收原因,但请注意,发生并发堆压缩时,由于使用了 GetPrimitiveArrayCritical,回收遭到阻止。一般情况下,强烈建议不要使用 GetPrimitiveArrayCritical,因为它在移动回收器方面具有限制。 - HeapTrim
这不是垃圾回收原因,但请注意,堆修剪完成之前回收会一直受到阻止。
- Concurrent
- 垃圾回收名称<GC_Name>concurrent mark sweep
ART 具有可以运行的多种不同的垃圾回收。- Concurrent mark sweep (CMS)
整个堆回收器,会释放和回收映像空间以外的所有其他空间。 - Concurrent partial mark sweep
几乎整个堆回收器,会回收除了映像空间和 zygote 空间以外的所有其他空间。 - Concurrent sticky mark sweep
生成回收器,只能释放自上次垃圾回收以来分配的对象。此垃圾回收比完整或部分标记清除运行得更频繁,因为它更快速且暂停时间更短。</
- Concurrent mark sweep (CMS)