写在前面
英文原版链接,若是觉得本文哪里不好还请指出,以便及时修改,以下是译文
安卓是为移动设备设计的,所以开发者应该时刻留意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了