最近在研究Android内存垃圾回收的内容,遇到一些自己之前不知道的技巧和方法。现在分享一种简单的在Logcat中可以看到垃圾回收状态的方法。经常关注Logcat日志的童鞋偶尔会看到一条类似于以下形式的记录。这种记录就是系统执行垃圾回收后返回的状态信息。
Dalvik虚拟机的Log信息
在Davlik虚拟机(非ART)中,每一次垃圾回收都会返回一条类似的信息。例子如下:
D/dalvikvm( 9050): GC_CONCURRENT freed 2049K, 65% free 3571K/9991K, external 4703K/5261K, paused 2ms+2ms
它们大致可以分成如下几个部分:
D/dalvikvm: <GC_Reason> <Amount_freed>, <Heap_stats>, <External_memory_stats>, <Pause_time>
各个字段的含义如下:
- <GC_Reason> GC的原因,也就是GC类别。它包含以下几种类别:
- GC_CONCURRENT 当堆将要被填满的时候触发的垃圾回收
- GC_FOR_MALLOC 当应用的堆已经被填满的时候,如果应用继续申请内存就会触发此类垃圾回收。系统会杀死应用的进程并且回收所有内存。
- GC_HPROF_DUMP_HEAP 当请求生成HPROF文件来分析内存的时候会触发此类垃圾回收
- GC_EXPLICIT 一次指定的垃圾回收,例如主动调用
System.gc()
的时候。(尽量避免此类调用,垃圾回收交给系统来做就可以了) - GC_EXTERNAL_ALLOC 在API版本10(Android3.0)以下的时候的垃圾回收机制。3.0以上版本所有的内存都在Dalvik堆中分配。它是用来回收dalvik虚拟机以外的内存(例如
Bitmap
中的内存或者NIO buffer中的内存)。
- <Amount_freed> 本次垃圾回收中释放的内存总量
堆中可用空间所占的百分比 和 (堆中对象的数量)/(堆的大小) 系统API版本10以下的系统中, Dalvik虚拟机堆外 (分配的内存) / (限制的内存量) 垃圾回收过程中应用暂停挂起的时间。Concurrent类型的垃圾回收有两次暂停时间:一次发生在开始,另一次发生在结束。堆的内容越多,暂停的时间越长。
观察这些Log信息,如果heap stats中的数值(堆中对象数量)/(堆的大小)越来越大,那么应用中很有可能存在内存泄漏。
ART的Log信息
不像Dalvik虚拟机,ART不会把所有的GC结果都输出到Logcat中。只有那些被认为执行缓慢的GC才会被输出到Logcat中。确切的说,只有GC停顿时间超过5ms或者整个GC耗时超过100ms才会被输出到Logcat中。(注意:3.0之后垃圾回收做了优化,整个GC过程中只有一小部分时间会导致应用停顿)。主动发起的GC一定会被输出到Logcat中。ART输出的Log信息的例子如下:
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
它的格式是这样的:
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)>
- <GC_Reason> 触发垃圾回收的原因以及触发了何种类型的垃圾回收,它包含以下几类:
- Concurrent 特点是不需要挂起应用线程。它在后台线程中运行,不会影响到内存的分配。
- Alloc 它在应用申请内存但是堆已满的情况下触发。在这种情况下,垃圾回收在分配内存的线程中进行。(它会导致应用暂停一段时间)
- Explicit 主动发起的垃圾回收,例如
System.gc()
。跟dalvik一样,建议不要主动发起垃圾回收。 - NativeAlloc 它会在native层内存吃紧的时候发起。比如说分配
Bitmap
或者RenderScript
内存空间不够的时候。 - CollectorTransition 一般由堆转换引起,垃圾回收器会把free-list back空间的所有对象都复制到bump pointer空间中。目前,转换过程只在一些低内存的设备上应用所在进程从对暂停敏感切换到对暂停不敏感状态的时候发生。
- HomogeneousSpaceCompact 它是在free-list 空间到free-list空间的复制。当app所在进程对暂停不敏感的时候发生。它可以减少内存的使用,减少内存分配的碎片化。
- DisableMovingGc 它并不是引起内存回收的真正原因,它是垃圾回收被GetPrimitiveCritical中断时发生的。当concurrent 堆压缩正在执行的时候,因为对垃圾回收器的限制,所以非常不建议使用它。
- HeapTrim 它不是触发垃圾回收的原因,但是在堆压缩的时候垃圾回收会被终止。
- GC Name 垃圾回收的名称,一共有如下几类:
- Concurrent mark sweep(CMS) 对整个堆进行垃圾回收,除了image空间。
- Concurrent partial mark sweep 对几乎整个堆进行回收,除了image空间和zynote空间。
- Concurrent sticky mark sweep 一次普通的垃圾回收,它只负责回收上次垃圾回收之后的分配的对象。它要比Concurrent partial mark sweep执行的次数频繁的多,因为它的执行速度快,暂停时间少。
- Marksweep + semispace 一种非同时进行的,包含复制过程的GC。可以用来移动堆,也可以用来压缩堆(减少堆的碎片化)。
- Objects freed 释放了对象(非大对象)的数量
- Size freed 释放了空间(非大对象)的大小
- Large objects freed 释放了大对象的数量
- Large object size freed 释放了大对象的空间的大小
- Heap stats 堆中空闲空间的百分比 和 (对象的个数)/(堆的总空间)
- Pause times 一般情况下,垃圾回收的暂停时间跟堆中引用的数量成正比。目前,ART CMS GC 只有一次在垃圾回收结束的时候。内存转移的GC在整个过程中有一个长时间的暂停。
同样,在使用ART的情况下,如果Logcat中看到大量的GC的记录。并且Heap stats信息中的(对象数/堆的空间)的数值不断增长,没有变小的趋势。那么应用很有可能存在内存泄漏。
如果看到GC Reason对应的信息变成了 “Alloc”,那说明应用的堆几乎满了,接下来马上要内存溢出了。