深入理解Java虚拟机-垃圾收集器

在前面的文章中,我们提到了几种常见的垃圾收集算法,今天咱们来说一说商用Java虚拟机中经典的垃圾收集器。

新生代收集器

我们首先介绍三种新生代的垃圾收集器,分别是Serial收集器、ParNew收集器和Parallel Scavenge收集器。

Serial收集器

深入理解Java虚拟机-垃圾收集器_第1张图片

Serial收集器是最古老的垃圾收集器,曾经是新生代垃圾收集的唯一选择。这是一种单线程的垃圾收集器,采用标记-复制算法,在进行垃圾收集时,所有的工作线程都会停止等待垃圾收集完成,也就是所谓的“Stop the world”。对于服务器来说,可能过长的暂停是不可接受的,但是对于短暂的暂停时间不敏感的客户端其实仍然是简单高效的选择。

ParNew收集器

深入理解Java虚拟机-垃圾收集器_第2张图片

ParNew收集器可以被看作是Serial收集器的多线程版本,比起Serial收集器并没有太大创新,只是把GC过程的单线程改成了多线程,同样也是采用复制算法。这里我们需要补充一点,当在单核CPU的场景下,由于线程切换必然会产生一定的开销,因此,在这种情况下,ParNew收集器的效率是不如Serial收集器的。

Parallel Scavenge收集器

同样的,Parallel Scavenge收集器也是多线程版本的收集器,因此垃圾收集过程和上述的ParNew收集器很类似。与CMS等关注工作线程停顿时间(提高用户体验)不同,Parallel Scavenge收集器的特点是非常关注吞吐量的大小(高效利用CPU),吞吐量的计算公式如下:

吞吐量=工作线程运行时间/CPU总运行时间

老年代收集器

几种常见的老年代垃圾收集器分别是Serial Old收集器、Parallel Old收集器和CMS收集器。

Serial Old收集器

单从名字来看,Serial Old收集器是Serial收集器的老年代版本,事实也确实如此,这款收集器是单线程,采用标记-整理算法的收集器。在JDK 5及以前的JDK中,Serial Old收集器常常与Parallel Scavenge收集器一同使用。

Parallel Old收集器

与Serial Old收集器类似,Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法收集垃圾。这款垃圾收集器是JDK 6才开始出现的,在此之前都是Serial Old收集器常常与Parallel Scavenge收集器搭配使用,但是这样的话,尽管新生代的垃圾收集是多线程的,但是老年代却是单线程的,因此实际上吞吐量可能并不是很高。Parallel Old收集器出现以后,配合Parallel Scavenge收集器才真正是高吞吐量的垃圾收集。

CMS收集器

CMS是一款划时代的垃圾收集器,它通过较短的停顿时间进行垃圾收集,是HotSpot的第一款真正意义上的并发收集器(用户线程和GC线程并发工作),还是一种“边污染边治理”的收集器。CMS收集器的垃圾收集分为四个步骤:

  • 初始标记

  • 并发标记

  • 重新标记

  • 并发清除

深入理解Java虚拟机-垃圾收集器_第3张图片

其中,初始标记和重新标记都需要停止其他工作线程。初始标记时利用可达性分析算法从GC Roots出发进行标记,整个过程耗时非常短,而重新标记是对并发标记过程中工作线程产生的垃圾进行收集。最后是并发-清除过程,由于不需要移动对象,因此GC线程与工作线程并发运行。

在整个CMS收集器收集垃圾的过程中,初始标记和重新标记的耗时是最短的,因此CMS是一款非常优秀的垃圾收集器。但是这款收集器就没有缺点吗?答案是有的。下面总结了几条CMS收集器的缺点:

  • 由于CMS采用标记-清除算法,因此会产生大量的内存碎片,将来需要分配大内存给某个对象时,可能没有足够的内存空间。

  • CMS收集器对处理器资源敏感,并发过程中CMS收集器的线程实际上是占据了一部分处理器资源的,因此造成吞吐量在一定程度上的下降。

  • 难以处理并发清除阶段产生的“浮动垃圾”。在并发清理阶段尽管会回收部分垃圾,但是此时工作线程也在运行,还在产生一些垃圾,但这些垃圾因为没有被标记,因此是不可回收的。这时如果工作线程分配新的对象而没有足够的内存空间时,就会发生“并发失败”(Concurrent Mode Failure),此时虚拟机将会把CMS收集器替换成为Serial Old收集器,并冻结工作线程。

G1收集器

G1收集器是一款面向服务器端的垃圾收集器,服务器端的多CPU和大容量内存能够满足G1收集器对于缩短GC停顿时间的需求。G1也采用分代理论,但是这些不再是之前固定大小和位置了,而是将堆内存划分为一个个小的内存块,每个内存块可以根据需求进行划分,下面的分区中Humongous区是用于存放大对象的区域。

深入理解Java虚拟机-垃圾收集器_第4张图片

G1收集器的垃圾收集过程也分为4个步骤:

  • 初始标记。初始标记与CMS类似,需要进行“Stop the world”,但是是借着Minor GC的过程进行的,因此没有额外的耗时。这个过程标记GC Roots可以关联的对象,然后修改TAMS(Top at Mark Start)的指针,指针划分每一个区域中的一部分空间用于并发回收过程中的新对象分配。

  • 并发标记。从GC Root开始对堆中对象进行可达性分析,找出要回收的对象,并且需要处理原始快照(SATB)记录的在并发时引用变动的对象。

  • 最终标记。需要“Stop the world”并进行并发标记遗留的SATB对象的处理。

  • 筛选回收。这里根据用户期望的停顿时间指定回收计划,将决定回收的区域的存活的对象复制到新的区域中,并清空旧的区域。这里同样暂停所有工作线程。

深入理解Java虚拟机-垃圾收集器_第5张图片

与CMS收集器的标记-清除不同,G1收集器宏观上看使用标记-整理算法,从局部来看又是使用的标记-复制算法。可以由用户指定期望的停顿时间是G1相较CMS的一个较大的优势,但是这里的停顿时间必须切合实际,因为停顿时间一定是有一个下限的,因此过小的停顿时间可能难以达到预期,甚至引发Full GC降低系统性能。

写在最后的话

这些年JDK版本迭代速度较快,涌现了一些非常优秀的垃圾收集器,本文只是介绍了一些经典的垃圾收集器,后续还会有一些优秀的收集器,比如Shenandoah收集器和ZGC收集器,这两款都是非常优秀的低延迟垃圾收集器,在今年的春招中有些公司已经开始问到相关的问题了。

你可能感兴趣的:(Java)