Android GC Log解读

写在前面

英文原版链接,若是觉得本文哪里不好还请指出,以便及时修改,以下是译文

安卓是为移动设备设计的,所以开发者应该时刻留意app占用的RAM(Random-Access Memory)。尽管Dalvik和ART会例行垃圾回收(GC),但并不代表开发者可以忽略app的内存使用情况(什么时候在哪里分配/释放了多少内存)。为了确保稳定的用户体验,让系统能够在多个app之间迅速切换,app不要在用户没有和它交互时消耗不必要的内存,这一点很重要

P.S.Dalvik(Android专用的JVM)、ART(Android Runtime),Android4.4引入了ART,之前是Dalvik

即便在开发期间遵循所有的最佳实践来管理app的内存(开发者理应这样做),也可能会出现对象泄漏或者其它内存bug。唯一可靠的,能尽量减少app内存占用的方法就是用工具分析app的内存使用情况,本篇指南将详细说明应该怎么做

Log信息解释

查看app内存使用情况最简单的方式是运行时的log信息,GC执行后,会输出一段信息到logcat。logcat的输出可以在Device Monitor里查看,或者直接在IDE比如Android Studio里查看

Dalvik Log信息

在Dalvik(不是ART)模式下,每次FC会在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,以及属于哪种类型的垃圾回收,可能出现的值包括:

  • GC_CONCURRENT

    heap快满了引起的并发GC

  • GC_FOR_MALLOC

    heap已经满了,此时app尝试分配内存,所以系统只好阻止app并回收内存,导致内存分配失败

  • GC_HPROF_DUMP_HEAP

    请求创建一个用来分析heap的HPROF文件,引发GC

    P.S.hprof文件描述了heap使用情况,类似于Chrome的heapsnapshot文件

  • GC_EXPLICIT

    显式GC,例如调用gc()(这种方式应该避免,要相信GC会在需要的时候自动执行)

  • GC_EXTERNAL_ALLOC

    外部内存分配(比如像素数据存储在native memory里或者NIO byte buffer)引起的GC,只会在API 10和更低版本中出现(新一点的版本所有东西都在Dalvik heap里分配)

Amount freed

本次GC回收的内存大小

Heap stats

heap中可用内存所占百分比,以及[已使用内存大小]/[heap总大小]

External memory stats

外部内存统计数据,

在API 10和更低版本中,外部分配的内存,以及[已分配内存大小]/[GC下限]

P.S.达到GC下限时进行GC

Pause time

heap越大,需要的停顿次数越多,并发GC(GC Reason为GC_CONCURRENT)的停顿次数分2部分:第一部分是GC开始时,另一部分是GC结束时

记下这些log信息,关注heap stats(例子中是3571K/9991K),如果持续增长,可能就存在内存泄漏了

ART Log信息

与Dalvik不同,ART不会log非显式(隐式)请求的GC,GC只会在被判定为很慢时输出信息。更准确地,条件是GC停顿超过5ms,或者GC耗时超过100ms。如果app不是处于一种可察觉的停顿状态,那么GC不会就被判定为很慢,而显式GC会被log出来

ART的GC log中包含了以下信息:

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

什么触发了GC,以及属于哪种类型的垃圾回收,可能出现的值包括:

  • Concurrent

    并发GC,不会挂起app线程,这种GC在后台线程中运行,不会阻止内存分配

  • Alloc

    GC被初始化,app在heap已满的时候请求分配内存,此时,GC会在当前线程(请求分配内存的线程)执行

  • Explicit

    GC被app显式请求,例如,通过调用System.gc()或者runtime.gc()。和Dalvik一样,ART建议相信GC,尽可能地避免请求显式GC。不建议使用显式GC,因为会阻塞当前线程,并引起不必要的CPU周期。如果GC导致其它线程被抢占的话,显式GC还会引发jank

    P.S.jank是指第n帧绘制过后,本该绘制第n+1帧,但因为CPU被抢占,数据没有准备好,只好再显示一次第n帧,下一次绘制时显示第n+1帧

  • NativeAlloc

    来自native分配的native memory压力引起的GC,比如Bitmap或者RenderScript对象

  • CollectorTransition

    heap变迁引起的GC,运行时动态切换GC造成的,垃圾回收器变迁过程包括从free-list backed space复制所有对象到bump pointer space(反之亦然)。当前垃圾回收器过渡只会在低RAM设备的app改变运行状态时发生,比如从可察觉的停顿态到非可察觉的停顿态(反之亦然)

  • HomogeneousSpaceCompact

    Homogeneous space compaction是free-list space与free-list space的合并,经常在app变成不可察觉的停顿态时发生,这样做的主要原因是减少RAM占用并整理heap碎片

  • DisableMovingGc

    不是一个真正的GC原因,正在整理碎片的GC被GetPrimitiveArrayCritical阻塞,一般来说,因为GetPrimitiveArrayCritical会限制垃圾回收器过渡,强烈建议不要使用

  • HeapTrim

    不是一个真正的GC原因,但GC被阻塞,直到heap trim结束

GC Name

ART有几种不同的GC

  • Concurrent mark sweep (CMS)

    全堆垃圾收集器,负责收集释放除image外的所有空间

  • Concurrent partial mark sweep

    差不多是全堆垃圾收集器,负责收集除image和zygote外的所有空间

  • Concurrent sticky mark sweep

    分代垃圾收集器,只负责释放从上次GC到现在分配的对象,该GC比全堆和部分标记清除(mark sweep)执行得更频繁,因为它更快而且停顿更短

  • Marksweep + semispace

    非并发的,复制堆过渡和homogeneous space compaction(用来整理heap碎片)使用的GC

Objects freed

本次GC从非大对象空间(non large object space)回收的对象数目

Size freed

本次GC从非大对象空间回收的字节数

Large objects freed

本次GC从大对象空间里回收的对象数目

Large object size freed

本次GC从大对象空间里回收的字节数

Heap stats

可用空间所占的百分比和[已使用内存大小]/[heap总大小]

Pause times

一般情况下,GC运行时,停顿次数和被修改的对象引用数成比例。目前,ART CMS GC只会在GC结束的时停顿一次,GC过渡会有一个长停顿,是GC时耗的主要因素

如果在logcat看到一堆GC信息,找到heap stats(例子中是25MB/38MB),如果持续增长从不减小,就可能存在内存泄漏。如果看到GC的原因是Alloc,那么说明heap已经要满了,快OOM了

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code