转载请注明原文地址:http://blog.csdn.net/mxm691292118/article/details/51006010
我把Android重难点和读书笔记都整理在github上:https://github.com/miomin/AndroidDifficulty
如果你觉得对你有帮助的话,希望可以star/follow一下哟,我会持续保持更新。
引言:我的上一篇博文提到JVM运行时的内存分块和GC的基本思想及算法,在GC的道路上越走越远,接下来要做的就是 深入浅出JVM的垃圾收集机制。
一、分代垃圾回收
1、新生代:大部分刚被创建的对象被分配到这里,生命周期短,被创建后很快变成不可达。在新生代区域内发生的GC称为minor GC。
新生代被划分为三个区域:
- 1个Eden空间
- 2个Survivor空间
在节点拷贝法中,不会简单的将内存划分为1:1的两份,而是划分成1个较大的Eden和2个较小的Survivor(HotSpot中默认为Eden的1/8)。
下面看新生代的GC过程:
- (1)大多数刚刚被创建的对象会存放在Eden。
- (2)在Eden执行第一次GC后,幸存的对象被移动到其中一个Survivor,之后的每次Eden满了都会执行GC,且都会将幸存者移动到该Survivor,直到该Survivor满(如果这时候Survivor不足以放下来自Eden的幸存者,会使用内存分配担保,提前进入老年代)。
- (3)当一个Survivor满了之后,将Eden和该Survivor中还存活的对象移动到另一块Survivor,清空Eden和原来的Survivor(这时候,如果Survivor的空间不足以存放所有的幸存者,会依赖老年代的内存进行分配担保,对于内存分配担保会面会详细讲解)。
- (4)在以上的步骤中重复几次依然存活的对象,就会被移动到老年代(默认15岁被移动到老年代)。
- (5)老年代也满了之后,会触发major GC或full GC。
2、老年代:从新生代的GC中存活下来的对象,会被拷贝到老年代,老年代空间比新生代大,发生在老年代上的GC要比新生代少。老年代中发生的GC被称为major GC。
3、思考一个问题:如果老年代的对象引用了一个新生代的对象,会怎么样?
- 老年代中存在一个Card Table,所有老年代的对象指向新生代对象的引用都会被记录在表中。新生代GC时,需要查询Card Table来决定是否可以被收集,而不用查询整个老年代。
- Card table做为一个index,每一个bit,都代表了老年代中的一块连续的区域。
- 在Update一个bit的时候,还使用了一种叫做wirte barrier的技术,在程序修改一个ref的内容的时候,可以被编译器得知,显著的提升GC性能。
- 注意:被老年代引用的新生代对象不会被GC,但是引用了老年代的新生代会被GC掉。
二、垃圾收集器类型
1、Serial收集器
- 单线程收集器,会在它进行GC时,暂停其他所有的工作线程 — “Stop The World(STW)”
- 从Serial到Parallel到CMS到G1,GC导致用户线程停顿的时间在不断缩短,但是没有完全消除。
- 用于新生代收集
2、ParNew收集器
- 是Serial的多线程版本
- 只是使用多线程GC,但是跟Serial一样,在GC时也必须暂停其他工作线程。
- 用于新生代收集
- 不适合单CPU环境
3、Parallel Scavenge收集器
- 与ParNew不同的是,具备自适应调节策略。
4、CMS(Concurrent Mark Sweep)收集器
- CMS出现的目标是取得最小短的“STW”的时间,因为在耗时最长的并发标记和并发清除期间,可以与用户进程同步执行。
Concurrent:并发,用户线程和垃圾收集线程交替执行,而不是并行,或者说运行在不同的CPU上。
GC过程
- 初始标记(需要STW):只是标记GC Roots直接关联到的对象,时间短。
- 并发标记:进行GC Roots Tracing的过程,时间比较长。
- 重新标记(需要STW):修正并发标记期间发送改变的对象的标记,时间也比较短。
- 并发清除:回收死去的对象。
缺点
- (1)对CPU资源敏感:在CPU数量比较少的情况下,CMS会占用加多资源,导致用户线程执行慢。通过i-CMS改善这个情况,让GC线程和用户线程交替执行,GC过程会变慢,但是对用户线程的影响比较小。
- (2)无法处理浮动垃圾:因为是并发的,在清理阶段,用户进程也会产生垃圾,这些垃圾无法在当次收集中被回收。所以CMS不能跟其他垃圾收集器一样,等到老年代占满后才触发GC,必须留一部分空间给浮动垃圾,如果在这个过程中浮动垃圾占满老年代剩余空间,会启动备用方案Serial收集器进行一次Full GC,停顿时间就更长了。
- (3)内存碎片:基于标记-清除算法,所以会产生碎片,导致Full GC提前发生。为了解决该问题,CMS可以在顶不住需要Full GC前执行一个碎片整理。(但是碎片整理不是并行的,会造成STW,所以不是每次Full GC都需要碎片整理)
5、G1收集器(未来代替CMS)
- 优势
- 与CMS一样的并发GC
- 不需要其他收集器配合,可以独立管理整个heap,自动协调进行分代收集(内部分配不同的Region,不需要连续的集合)
- 用的标记-整理和节点拷贝算法,不会产生内存碎片,减少GC频率
- 不够成熟,暂时没有实际运用
三、GC的触发条件
1、触发Minor GC(清理新生代对象)
- 分配对象时,发现Eden区满
- Eden区的对象进入Survivor时,发现其中一个Suvivor区满
- 在Full GC中配置,在每次Full GC前进行一次Minor GC
2、触发Major GC / Full GC(清理老年代对象)
- 分配对象时,发现老年代不足(CMS除外)
- CMS清理过程中,浮动垃圾把剩余的老年代空间占满,会启动Serial收集器,触发一次Full GC
- 老年代碎片严重,无法存入大对象,会提前触发Full GC
四、总结
1、新分配的对象进入Eden区
2、大对象直接进入老年代
- Eden区不够,会触发GC,为了避免频繁GC,避免Eden和Survivor之间进行没必要的复制,所以考虑直接进入老年代(可以通过参数配置对象阈值)
3、长期存活的对象直接进入老年代
- 如果在Eden中经过第一次Minor GC后还能存活,并且Survivor可以存放该对象,就将对象移入Survivor,Age设为1,在Survivor中每经过一次Minor GC,Age都会加1。(可以配置Age阈值)
- 动态对象年龄判定:没必要一定等到Age达到阈值,如果Survivor中相同Age的对象的大小总和大于Survivor的一半,Age大于等于该Age的对象都进入老年区。