内存回收机制
![](https://img.haomeiwen.com/i9340319/ed04492c5131b7b7.png)
看上图,可以看出垃圾回收机制的几个区域
通过内存图可以看出,内存分为三个区域:
-
Young Generation:年轻一代(不断更替,当年轻一代eden区域满了的情况下,还存活的对象被复制到Survivor0区域空间中,当Survivor0区域满了,此区存活的对象会被复制到另一个Survivor1区域,当Suevivor1也满了的时候,从Survivor0中复制还存活的对象到Old Generation中,其余的回收,这时候Survivor1和Sorvivor0角色互换)
-
Old Generation:老年一代(当年青一代中Survivor0和Survivor1转换一定次数后,还存活的对象被复制到Old Generation中,此时生命周期比较长)
-
Permanet Generation:持久一代(当存活的对象在Old Generation区域停留时间达到一定程度后,会被移动到Old Generation,最后累积一定时间再移动到Permanent Generation区域,几乎免疫内存垃圾回收器,活的最长久的一代人)
-
系统在Young Generation 和 Old Generation上采用不同的回收机制,每一个Generation都有一个固定的大小,随着新对象的不断被分配到此区域,当对象总的大小临近这一区域的内存阈值的时候,就会触发GC操作,以便腾出内存空间来存放新的对象
-
执行GC占用的时间和它在哪个Generation区域有关,Young Generation执行时间最短,Old Generation其次,Permanent Generation执行时间最长,同时Gc的执行时间也和当前Generation中对象的数量有关,Generation中对象数量越多,执行时间越长
垃圾回收机制算法: -
针对Young Generation 通常存活时间最短,因此基于Copying算法回收,所谓的Copying算法就是扫描出存活的对象,并复制到一块新的完全未使用的空间中,对应于Young Generation,就是在eden、FromSpace或TopSpace之间copy,新一代采用空间指针的方式控制Gc触发,指针保持最后一个分配的对象在Young Generation区间的位置,当有新的对象要分配内存时,检查内存空间是否足够,不够就触发Gc,当连分配对象时,对象会逐渐从eden到Survivor,最后到Old Generation
-
Old Generation相比较Young Generation存活时间较长,比较稳定,所以采用标记算法来回收。所谓的标记就是标记出存活的对象,然后回收未被标记的对象,回收后对空的内存空间要么进行合并、要么标记出来以便于下次进行分配,以减少内存碎片带来的效率损耗。
android系统中Gc有三种类型:
- kGcCauseForAlloc:在分配内存时内存不足的情况下引起的Gc,这种情况下Gc会stop world ,stop world是由于并发Gc时,其它线程都会停止工作。
- kGcCauseBackground:当内存达到一定的阈值的时候引发的Gc,这个时候一个后台Gc不会引起stop world。
- kGcCauseExplicit:显示调用时进行的Gc,如果ART打开了这个选项,在system.gc()时会进行Gc
其它Gc应该注意的情况:
- 尽量不去显式的调用gc,减少不必要的开销,影响应用的流畅度
- 尽量减少内存泄漏,避免OOM
常见的内存泄漏原因:
-
list map容器中有大量对象,容器使用完没有及时的清理
-
类的静态变量,持有大数据对象(bitmap)
-
Handler临时性的内存泄漏(内部类持有外部类)
-
改变哈希值
-
非静态内部类的静态实例
-
资源对象未关闭(数据库操作、网络、IO输入输出流连接)
-
注册的对象为注销()
-
静态集合类:容器为静态,那么它们的生命周期与程序一致,容器中的对象不会在程序结束前释放,从而造成内存泄漏
-
各种连接:数据库、网络、IO连接等,不用或者使用完毕就要释放关闭
-
变量不合理的作用域:一个变量的定义范围大于其使用范围,很有可能造成内存泄漏(比如:局部变量赋值内容,紧接着可以保存数据库,然而保存后,该变量随着方法的结束而释放,如果把局部变量,改成成员变量,那么保存数据库后,该变量生命周期同对象,方法结束后变量不能回收,一次造成泄漏)
-
内部类持有外部类
-
改变哈希值:当一个对象被存储进HashSet集合中,就不能修改这个对象中那些参与计算哈希值的字段,否则修改后的对象的哈希值与最初存储进HashSet中的哈希值就不同了,这就导致删除当前对象失败,造成内存泄漏
注意事项避免内存消耗过多:
- AutoBoxing自动装箱过程
- 内容复用
(1).有效利用系统自带的资源
(2).视图复用(viewholder)
(3).对象池
(4).Bitmap对象复用 - 使用最优的数据类型
(1).当对象的数目在1000以内且特别多访问而删除和插入不高的情况尽量用ArrayMap代替HashMap
(2).枚举最大的有点就是安全、易读,但是内存消耗是定义常量的三倍以上,可以用注解的方式来检查安全
(3).使用IntDef和StringDef来检查类型安全
(4).LruCache建议使用这个缓存机制,但是既不能分配太大,也不能分配太小 - 图片内存优化
(1).使用位图规格,使用inSampleSize实现位图的缩放和压缩。使用缓存机制等
Android虚拟机中有几种类型GC日志:
- GC_CONCURRENT:当应用进程中的Heap内存占用上涨时,避免因Heap满了而触发的GC
- GC_FOR_MALLOC:因GC_CONCURRENT没有执行完,而应用又需要更多的内存,这时就不得不停下来进行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
-
在DVM虚拟机下,GC都是并发的,也就是说每次触发GC都会导致其它线程工作的暂停(包括UI线程)。而在ART模式下,在GC时,不像DVM仅有一种算法,ART在不同的情况下会选择不同的回收算法
-
在GC方面,相比DVM,ART更为高效,不仅仅是GC的效率大大缩短了Pause的时间,而且在内存分配上对大内存分配单独的区域,有算法在后台做内存整理,减少内存碎皮,因此在ART模式下,避免较多的类似GC导致的卡顿问题。
内存分析工具:
Memory Monitor:内存、CPU、网络的分析工具