前言:话接上篇讲了垃圾收集器的几大算法,本篇主要讲对应算法的一些实现。话不多说,先看一张图。
图中主要介绍了目前主流的几款垃圾收集器(图中连线表示可以组合收集,不过CMS到Serial Old除外,我标为了红色)。其中Serial,ParNew,Parallel主要负责对年轻代的垃圾回收,CMS,Serial Old,Parallel Old,则是主要对老年代的垃圾回收,G1垃圾收集器就比较厉害,年轻代和老年代都由他收集。
就目前而言,没有万能的垃圾收集器,我们需要依据自身的业务情况来选择不同的垃圾收集器。因此清楚掌握每一种垃圾收集器的优缺点也是我们Java开发人员需要学习的一项技能。(PS:方便可以吹水)
一,Serial收集器
串行垃圾收集器。当他进行垃圾收集时,会直接暂停所有用户线程,直到收集结束,通俗地说就是一刀切。我们看下运行图:
Serial + Serial Old 收集器他在收集过程中不光Stop The World,而且他本身还是单线程去收集垃圾对象(年轻代采用复制算法,老年代采用标记整理算法),总体来说serial收集器结构比较简单,垃圾回收的速率比较慢,频繁的STW体验也不友好。
二,Parallel收集器
随着我们服务器配置越来越高级,我们上面说了Serial收集器是单线程收集,那跟现在的配置相比就显示有点能力跟不上了。正因如此,就出现了Parallel收集器(平行垃圾收集器)。他是可以多线程并发进行垃圾收集,就像一把散弹枪一样,我们看下图:
Parallel + Parallel Old收集器同样也会直接暂停用户线程(STW),默认的收集线程跟cpu核数相同,也可以指定收集线程数(-XX:ParallelGCThreads),但一般默认不改他。
三,ParNew收集器
ParNew收集器与Parallel很类似,这里就不画图了,他们区别就在于ParNew可以与我们接下来讲的CMS垃圾收集器组合使用
四,CMS收集器(Concurrent Mark Sweep)
关于CMS收集器在面试中一般就是重灾区了。因为他用起来是真香。像上面三种收集器,他们整个垃圾收集过程几乎都是STW的,虽然专一,吞吐量高,但同时停顿时间长,对于如今的互联网环境,系统卡顿几乎是一件让人无法忍受的事情。而CMS不一样,他非常注意用户的体验,它第一次实现了gc线程与用户线程同时工作(几乎)。
初始标记:暂停所有的用户线程,并标记GC Root的直接可达对象,速度很快。
并发标记:从GC Root的直接关联对象向下遍历所有对象,所以过程耗时比较长,但是此处不需要停止用户线程,可以同时与gc线程一同运行。
重新标记:因为在并发标记过程中,很可能对象的状态已经发生变动(对象在并发过程中是可达的,但是在并发结束后马上变成垃圾对象了),所以此步骤是一个查漏补缺的操作,但是相比较于并发标记STW,此步骤耗时就少多了。
并发清理:GC开始对垃圾对象进行回收,并且用户线程也可以同时运行。
并发重置:重置本次GC过程中的标记数据。
CMS收集器是一种 “标记-清除”算法实现的,它相比较前面几种垃圾收集器来说算法就更加复杂。但他的优点也是显示易见的,他的停顿点就只有一开始的初始标记和后面的重新标记,这两点的处理速度是非常快速的,所以用户就更容易接受,仅管CMS的GC时间可能比上面几种垃圾收集器的时间更长。
但CMS收集器也不是毫无毛病的,他的缺点也比较明显:
1,CMS在并发标记和并发清理过程中会产生的垃圾就没办法在本次gc过程中回收,只能等到下次gc清理,所以会产生浮动垃圾。
2,因为CMS使用标记清除算法,所以会产生大量的空间碎片,但是他有参数可以设置在gc完成后进行整理(-XX:+UseCMSCompactAtFullCollection)
3,我们都知道gc是因为内存空间快要消耗完了我们才会进行回收,但由于CMS在gc过程中用户线程还是运行的,如果gc过程中用户线程以产生了一个大对象放到堆里面,堆空间放不下了,这时就会再次触发“concurrent mode failure”,直接导致STW,然后改为serial old收集器进行回收(本节第一个图中用红线连接的情况)
总结:尽管垃圾收集器的出现使用我们程序员不用再去做手动释放内存这种劳心劳力的操作,但并不代表我们就可以完全不用了解他,只做一个拿来主义者。相反这是你区别于其他开发人员的一个关键指标(都给我卷起来)。好了今天就到这里啦,下期我们接着讲一下G1收集器,我是阿雷,一个只敢喝无糖可乐的程序员。