经典垃圾收集器直接的关系
图中连线表示它们可以搭配使用,Serial-CMS组合以及ParNew-Serial Old组合在JDK 9时已经被取消了。
CMS与Serial Old直接也有连线,这是因为在CMS发生并发失败的时候(Concurrent Mode Failure)就会用备用方案Serial Old来进行收集
Serial
Serial是一个单线程工作的收集器,在它开始收集的时候需要暂停全部的用户线程,直到它收集结束。
迄今为止,它依旧时Hotspot虚拟机运行在客户端模式下的默认新生代收集器,它有以下特点:
- 简单而高效
- 额外内存消耗少
ParNew
ParNew实现和Serial非常的类似,甚至共用了很多的代码,与Serail不同的是,ParNew在收集的时候会用多个线程并行的进行收集。
真正让ParNew被广泛使用的原因,是因为处理Serial,它是唯一一个可以配合CMS进行使用的收集器了(ParNew也只有在老年代用CMS收集器的时候才有用了),Serial-CMS组合在JDK8就不推荐使用了,在JDK9直接被移除,也就是如果用CMS,那年轻代就只能用ParNew。
然而成也萧何败也萧何,由于G1收集器慢慢的替代了CMS,ParNew注定要变成第一个退出历史舞台的收集器。
Parallel Scavenge
Parallel Scavenge同样是新生代的垃圾收集器,通过用的复制算法,也同样是多线程并行收集,和ParNew非常相似,只是它的关注点与其他收集器不同,CMS等收集器关注的是怎么样减低用户线程的停顿时间,而Parallel Scavenge关注的则是达到一个可控制的吞吐量。
所谓吞吐量就是处理器用于运行用户代码的时间和处理器消耗时间的比值:
-XX:MaxGCPauseMillis:控制最大垃圾收集停顿时间,MaxGCPauseMillis设置得稍小,停顿时间可能会缩短,但是会导致垃圾收集发生得更频繁,使得吞吐量下降。
-XX:GCTimeRatio:设置垃圾收集时间占总时间的比率(0<n<100的整数),计算为1 / (1 + n),例如,-XX:GCTimeRatio=19,设置了垃圾收集时间占总时间的5% = 1/(1+19)。
Serial Old
Serial Old是Serial的老年代版本,它可以搭配Parallel Scavenge进行使用,但是更多的是做为CMS发生并发失败的时候(Concurrent Mode Failure)的备用方案,其使用的是标记整理算法。
Parallel Old
Parallel Scavenge的老年代版本。
CMS
CMS是一款跨时代的垃圾收集器,它以用户线程最短的停顿时间作为目标,CMS是一款老年代的回收器,采用标记-清除算法。
- 初始标记:这一步是要停掉所有用户现象的(STW),标记GC Roots直接关联的对象,速度很快。
- 并发标记:这一步是沿着引用链向下进行对象的标记,速度比较慢,CMS把这个步骤与用户线程可以并发执行,不需要暂停用户线程。由于这一步用户线程也在执行,所以可能会出现这些问题:
(1)应该删除的垃圾没有删除(浮动垃圾),CMS不做处理,下一次在收集。
(2)不应该删除的对象被删除,CMS通过增量更新的方法解决了这个问题。 - 重新标记:并发标记中使用了增量更新的方法,会标记一些引用发生变更了的黑色对象(三色标记算法),这些对象要重新进行垃圾扫描分析。
- 并发清理:由于不需要移动对象(标记-清除),所以这部分可以与用户线程并发执行。
CMS造成的问题
- 虽然并发标记阶段是和用户线程并发执行的,但是运行GC线程同样要消耗CPU资源,CMS默认启动的GC线程数量 = (处理器核心数量 + 3)/ 4,可以看出当处理器核心数量大于等于4个的时候,垃圾线程占用25%处理资源;但当处理器核心数量小于4的时候,甚至要分出一半的CPU资源来进行回收。
- CMS会产生浮动垃圾。
- CMS采用标记-清除算法,还产生空间碎片,这是如果分配一个大对象,就可能出现明明有空间,却都是碎片而无法分配的情况,这样虚拟机就不得不进行一次完全STW的Full GC(Serial old,CMS有参数可以设置在经过多少次不整理的Full GC之后进行一次整理的Full GC)
- 由于CMS会和用户线程并发执行,所以不能再内存区域打到100%的时候再回收,JDK5的时候,老年代对象占用达到68%时就会触发GC,而到JDK6则是提高到了92%,这样又会有新的风险,可能CMS还没回收完毕,内存就满了,这个时候就不得不进行一次完全STW的Full GC。
G1
G1是垃圾收集器技术的里程碑式的结果,开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。在G1出现之前,所有的垃圾回收器都是进行全代的回收(回收年轻代,回收老年代)。
面向局部收集
G1将堆分成多个Region,Region可以扮演eden区、survivor区、old区、humongous区。G1在进行垃圾回收的时候,不在依据某一个代来做回收衡量,而是衡量那一块Region中垃圾最大,回收收益最大来进行回收(Mixed GC)。
Humongous区
humongous区是Region的一种类型,专门用来储存大对象的,G1认为只要一个对象大小超过Region的一半,就认为是一个大对象,如果一个对象是一个超大的对象,那就要用多个连续的humongous区来储存。
跨Region引用
和以前的垃圾收集器一样存在跨代引用一样,G1要解决跨Region引用的问题,G1的每一个Region都自己维护了一张记忆表,所以大概要消耗堆大小10% - 20%的容量来维持收集器工作。
TAMS指针
G1在进行垃圾回收的并发标记的时候,用户线程也在运行,此时会产生新的对象,TAMS指针就是用来解决新对象的内存分配问题,TAMS标志着Region的空白区域,新对象要在TAMS标记的区域中分配内存。
G1垃圾回收过程
- 初始标记:标记GC Roots可以直接关联到的对象,并确认TAMS指针的位置,需要暂停用户线程。
- 并发标记:沿着引用链递归进行扫描。
- 最终标记:对原始快照进行重新扫描。
- 筛选回收:对每一个Region的回收收益进行排序,根据停顿时间来决定回收计划,把回收Region中的存活对象移动到一个空的Region中,再清掉一整个旧的Region。