经典的垃圾回收器大概有7种,这些收集器的目标、特性、原理、使用场景都有所区别,有的适用于年轻代,有的适用于老年代,图中展示的就是这7中垃圾回收器,如果两个垃圾回收器有连线,则表明可以配合使用。这个关系不是一成不变的,Serial+CMS、 ParNew+Serial Old本来也可以组合的,在JDK8、JDK9被声明废弃和取消。这篇文章主要讨论CMS、G1
1、Serial收集器
Serial收集器是最基础、历史最悠久的收集器,是一个单线程工作的收集器,单线程并不仅仅说明它只会使用一个处理器或者一条线程区完成垃圾收集工作,更重要的是它在垃圾回收时,必须暂停用户进程,Stop The World。
适用场景:客户端模式下新生代收集器
Serial虽然是单线程的,也是有优于其他收集器的地方,简单高效,对内存资源受限的环境,额外消耗内存最小的,所以适用于客户端模式的新生代。
2、ParNew收集器
ParNew是serial的多线程并行版本,除了使用多条线程进行垃圾收集之外,其他和Serial都一样。
使用场景:JDK7之前遗留系统的首选新生代收集器
其中有一个与功能、性能无关但其实很重要的原因是:除了Serial收集器外,目前只有它能与CMS 收集器配合工作。
3、Parallel Scavenge收集器
Parallel Scavenge收集器同样是新生代收集器,基于标记-复制算法实现,也是并行收集。Parallel Scavenge的目标是达到一个可控制的吞吐量
如果虚拟机完成某个任务,用户代码加上垃圾收集总共耗费了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。停顿时间越短就越适合需要与用户交互或需要保证服务响应质量的程序,良好的响应速度能提升用户体验;而高吞吐量则可以最高效率地利用处理器资源,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的分析任务。
Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。
Parallel Scavenge收集器还有一个参数-XX:+UseAdaptiveSizePolicy值得我们关注 这是一个开关参数,当这个参数被激活之后,就不需要人工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象大小(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时 间或者最大的吞吐量。这种调节方式称为垃圾收集的自适应的调节策略
4、Serial Old收集器
Serial Old收集器是Serial的老年代版本,同样是单线程收集器,使用标记-整理算法。
使用场景:客户端模式
5、Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法。
Parallel Old 收集器是直到JDK 6 时才开始提供的,在此之前,新生代的 Parallel Scavenge 收集器一直处于相当尴尬的状态,原因是如果新生代选择了Parallel Scavenge 收集器,老年代除了 Serial Old ( PS MarkSweep)收集器以外别无选择,其他表现良好的老年代收集器,如 CMS 无法与它配合工作。由于老年代Serial Old 收集器在服务端应用性能上的 " 拖累 " ,使用 Parallel Scavenge 收集器也未必能在整体上获得吞吐量最大化的效果。
直到Parallel Old 收集器出现后, " 吞吐量优先 " 收集器终于有了比较名副其实的搭配组合,在注重 吞吐量或者处理器资源较为稀缺的场合,都可以优先考虑Parallel Scavenge 加 Parallel Old 收集器这个组合。
6、CMS收集器
CMS收集器是一种以获取最短回收停顿时间为目标的收集器,目前集中运行在互联网网站或者基于浏览器的B/S系统的服务端上,这类应用关注服务的响应速度,希望系统停顿时间尽可能短,CMS适用这种场景。CMS基于标记-清除算法实现。
CMS收集过程四个步骤:
- 初始标记
- 并发标记
- 重新标记
- 并发清除
初始标记 和重新标记需要Stop The World
初始标记:只是标记一下GC Root能直接关联的对象,速度很快。
并发标记:从GC Root能直接关联的对象开始,遍历整个引用链,这个过程虽然时间长,但是不需要停顿用户进程。
重新标记:修正并发标记期间,用户程序继续运行而导致编辑产生那一部分对象的标记记录,修正期间也是Stop The World。
并发清除:删除标记阶段判断已经死亡的对象,由于不需要移动存活对象,所以不需要停顿用户进程。
CMS优点:并发收集、低停顿。
7、G1收集器
在G1出现之前,其他的收集器目标范围都是整个新生代或者整个老年代,或者整个Java堆,而G1跳出了整个限制,面向堆内存任何部分来组成回收集进行回收,标准不再是哪个分代,而是那块内存中存放的垃圾数量最多,回收收益最大。
G1是基于Region的堆内存布局来实现任何内存部门来回收,把Java堆划分为多个大小相等的独立区域(Region),每个区域都可以根据需要扮演Eden、Survivor或者是老年代
Region中还有一类特殊的Humongous区域,专门存储大对象,只要超过一个Region容量的一半,就会判定为大对象,Region的大小可以通过参数-XX:G1HeapRegionSize设
定,取值范围为1MB~32MB。
G1回收过程四个步骤:
- 初始标记 :仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。
- 并发标记:从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。
- 最终标记:对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。
- 筛选回收:负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。