一、垃圾收集算法的基础:分代收集理论。
1、标记-复制算法
2、标记-清除算法
两个明显的问题
(1)效率问题(如果需要标记的对象太多,效率不高)
(2)空间问题(标记清除后会产生大量不连续的碎片)
3、标记-整理算法
二、垃圾收集器
1、Serial收集器(-XX:+UseSerialGC -XX:+UseSerialOldGC)
Serial(串行),单线程收集器,在进行垃圾收集的时候必须暂停其它所有的工作线程(“Stop The World”),直到它收集结束。
新生代采用复制算法,老年代采用标记-整理算法。
Serial Old收集器的用途:作为CMS收集器的后备方案。
2、Parallel Scavenge收集器(-XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代))
新生带采用复制算法,老年代采用标记-整理算法
Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)。
CMS等垃圾收集器的关注点更多是用户线程的停顿时间(提高用户体验)。
这里说的吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。
ParallelOld收集器是Parallel Scavenge收集器的老年代版本。使用多线程和标记-整理算法。在注重吞吐量以及CPU资源的场合,都可以优先考虑Parallel Scavenge收集器和Parallel Old收集器(JDK8默认的新生代和老年代收集器)
3、ParNew收集器(-XX:+UseParNewGC)
ParNew收集器其实跟Parallel收集器很类似,区别主要在于它可以和CMS收集器配合使用。
新生代采用复制算法,老年代采用标记-整理算法。
4、CMS收集器(-XX:+UserConcMarkSweepGC(old))
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用,它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
CMS收集器是一种“标记-清除”算法实现的。
初始标记:暂停所有的其他线程(STW),并记录下gc roots直接能引用的对象,速度很快。
并发标记:从GC Roots直接关联对象开始遍历正规对象图的过程。这个过程好像比较长,但不需要停顿用户线程。因为用户程序继续运行,可能会有导致已经标记过的对象状态发生改变。
重新标记:暂停所有的其他线程,这个阶段就是为了修正并发标记期间因为用户程序基础运行而导致标记产生变动的那一部分对象的标记记录。这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发阶段时间短。主要用到三色标记里的增量更新算法做重新标记。
并发清理:开启用户线程,同时GC线程开始对未标记的区域做清理。这个阶段如果有新增对象会被标记为黑色不做任何处理。
并发重置:重置本次GC过程中的标记数据。
CMS收集器的优点:并发收集、低停顿。
缺点:
对CPU资源敏感(会和服务抢资源)
无法处理浮动垃圾
使用的回收算法(标记-清除)会导致收集结束时会有大量空间碎片产生,当然可以通过参数,让JVM在执行完标记清除后再做整理。
执行过程中的不确定性,会存在上一次垃圾回收还没执行完,然后垃圾回收又被触发的情况,特别是在并发标记和并发清理阶段会出现,一边回收,系统一边运行,也许没回收玩就再次触发full gc,也就是concurrent mode failuer",此时会进入stop the world,用serial old垃圾收集器来回收。
三、垃圾收集底层算法实现
三色标记:
在并发标记的过程中,因为标记期间应用线程还在继续跑,对象间的引用可能发生变化、多标和漏标的情况就有可能发生。
(1)黑色:
表示类对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。安全存活
(2)灰色:
表示对象已经被垃圾收集器访问过,但这个对象至少存在一个引用还没有被扫描过。
(3)白色:
表示对象尚未被垃圾收集器访问过。
多标-浮动垃圾:
在并发标记过程中,由于方法运行结束导致部分局部变量被销毁,这个gcroot引用的对象之前又被扫描过(被标记为非垃圾对象),那么本轮GC不会回收这部分内存。
漏标-读写屏障
漏标会导致被引用的对象被当成垃圾误删除。有两种解决方案:增量更新和原始快照
增量更新:当黑色对象插入新的指向白色队形的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。简单理解:黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象。
原始快照(SATB):当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。这样就能扫描到白色的对象,将白色对象直接标记为黑色(目的就是让这种对象在本轮GC清理中能存活下来,待下一轮gc的时候重新扫描,这个对象也有可能是浮动垃圾)
对于读写屏障,以java hotspot vm为例,其并发标记时对漏标的处理方案如下:
CMS:写屏障+增量更新
G1,Shenandoah:写屏障+SATB
ZGC:读屏障
为什么G1用SATB?CMS用增量更新?
SATB(原始快照)相对增量更新效率比较高(当然SATB可能会造成更多的浮动垃圾),因为不需要在重新标记阶段再次深度扫描被删除引用对象,而CMS对增量引用的根对象会做深度扫描,G1因为很多对象都位于不同的region,CMS就一块老年代区域,重新深度扫描对象的话,G1的代价会比CMS高,所以G1选择SATB不深度扫描对象,只是简单标记,等到下一轮再深度扫描。