本平台的文章更新会有延迟,大家可以关注微信公众号-顾林海,包括年底前会更新kotlin由浅入深系列教程,目前计划在微信公众号进行首发,如果大家想获取最新教程,请关注微信公众
通过下图,知道Android系统中最近分配的对象会存放在Young Generation区域。对象在某个时机触发GC回收垃圾,而没有回收的就根据不同规则,有可能被移动到Old Generation,最后累计一定时间再移动到PermanentGeneration区域。系统会根据内存中不同的内存数据类型分别执行不同的GC操作。GC通过确定对象是否被活动对象引用来确定是否收集该对象,进而动态回收无任何引用的对象占据的内存空间。
图中整个内存分为三个区域:年轻代、老年代以及持久代。
1、Young Generation
年轻代分为三个区,一个Eden区,另外两个S0和S1都是Survivor区(S0和S1只是为了说明,两者实质上一样)。程序中生成的大部分新的对象都在Eden区,当Eden区满时,还存活的对象将被复制到其中一个Survivor区,当此Survivor区的对象占用空间满时,此区存活的对象又被复制到另一个Survivor区,当这个Survivor区也满时,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制到老年代。
2、Old Generation
老年代存放的是上面年轻代复制过来的对象,也就是在年轻代中还存活的对象,并且区满了复制过来的。一般来说,年老代中的对象生命周期都比较长。
3、Permanent Generation
用于存放静态的类和方法,持久代对垃圾回收没有显著的影响。
内存对象的处理过程如下:
-
对象创建后在Eden区。
-
执行GC时,如果对象仍然存活,则复制到S0区。
-
当S0区满时,该区存活对象将复制到S1区,然后S0区清空,接下来SO和S1角色互换。
-
当第3步达到一定次数后,存活对象将被复制到Old Generation。
-
当这个对象在Old Generation区停留的时间达到一定程度时,它会被移动到Old Generation,最后累积一定时间再移动到Permanent Generation区域。Permanent Generation区域存放一些静态文件,如Java类等。
系统在Young Generation、Old Generation上采用不同的回收机制。每一个Generation的内存区域都有固定的大小。随着新的对象陆续被分配到此区域,当对象总的大小临近这一级别内存区域的阈值时,会触发GC操作,以便腾出空间来存放其它新的对象。
执行GC占用的时间和它发生在哪一个Generation也有关系,Young Generation中的每次GC操作时间最短的,Old Generation其次,Permanent Generation最长。同时GC的执行时间也和当前Generation中的对象数量有关,Generation中的对象数量越多,执行时间越长。
Young Generation通常存活时间较短,因此基于Copying算法来回收,所谓Copying算法,就是扫描出存活的对象,并复制到一块新的完全未使用的空间中,对应于Young Generation,就是在Eden、FromSpace或ToSpace之间copy。新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在Young Generation区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。当连续分配对象时,对象会逐渐从Eden到Survivor,最后到Old Generation。
Old Generation与Young Generation不同,对象存活的时间比较长,比较稳定,因此采用标记算法来回收。所谓标记,就是扫描出存活的对象,然后再回收未被标记的对象,回收后对空出的空间要么合并,要么标记出来便于下次分配,以减少内存碎片带来的效率损耗。
在Android系统中,GC有以下三种类型:
-
kGcCauseForAlloc:在分配内存时发现内存不够的情况下引起的GC,这种情况下的GC会Stop World。Stop World时由于并发GC时,其他线程都会停止,知道GC完成。
-
kGcCauseBackground:当内存达到一定的阈值时触发GC,这个时候时一个后台GC,不会引起Stop World。
-
kGcCauseExplicit:显示调用时进行的GC,如果ART打开了这个选项,在system.gc时会进行GC。
分析下面这段虚拟机打印出来的日志
D/dalvikvm(7030):GC_CONCURRENT freed 1049k, 60% free 2341k/9351k, external 3502k/6261k, paused 3ms 3ms
freed 1049k表面在这次GC中回收了多少内存。
60% free 2341k/9351k是Heap的一些统计数据,表面这次回收后60%的Heap可用,存活的对象大小为2341KB,Heap大小是9351 KB。
external 3502k/6261k是Native Memory的数据,存放位图数据或者堆以外内存之类的。第一个数字表面Native Memory中已经分类多少内存,第二个值有点类似一个浮动的阈值,表面分配内存达到这个值,系统就会触发一次GC进行内存回收。
paused 3ms 3ms表面GC暂停的时间。
GC_CONCURRENT是当前GC时的类型,在Android的虚拟机中GC日志有以下几种类型:
-
GC_CONCURRENT:当应用进程中的Heap内存占用上涨时,避免因Heap内存满了而触发的GC。
-
GC_FOR_MALLOC:这是由于Concurrent GC没有及时执行完,而应用又需要分配更多的内存,这时不得不停下来进行Malloc GC。
-
GC_EXTERNAL_ALLOC:这是为external分配的内存执行达到GC。
-
GC_HPROF_DUMP_HEAP:创建一个HPROF profile的时候执行。
-
GC_EXPLICIT:显式地调用了System.gc()。一般来说,可以信任系统的GC机制,尽量不去显式调用System.gc(),减少不必要的系统开销,影响应用的流畅度。
在ART模式下日志多了一个Large Object Space(大对象占用的空间),这部分内存并不是分配在堆上的,但仍属于应用程序内存空间,主要用来管理Bitmap等占内存大的对象,避免因分配大内存导致频繁GC。
在Dalvik虚拟机下,GC的操作都是并发的,也就意味着每次触发GC都会导致其他线程暂停工作(包括UI线程)。而在ART模式下,在GC时,不像Dalvik仅有一种回收算法,ART在不同的情况下会选择不同的回收算法,比如Alloc内存不够时会采用非并发GC,但在Alloc后,发现内存达到一定阈值时又会触发并发GC。所以在ART环境下并不是所有的GC都是非并发的。
总体来看,在GC方面,与Dalvik相比,ART更为高效,不仅仅是GC的效率大大缩短了Pause时间,而且在内存分配上对大内存分配单独的区域,有算法在后台做内存整理,减少内存碎皮。因此在ART虚拟机下,可以避免较多的类似GC导致的卡顿问题。
以上内容来自《Android应用性能优化最佳实践》
搜索微信“顾林海”公众号,定期推送优质文章。