由于我们的业务量非常大,响应延迟要求高。目前沿用的老的ParNew+CMS已经不能支撑业务的需求。平均一台机器在1个月内有1次秒级别的stop the world。对系统来说是个巨大的隐患。所以我们采用测试环境压测和逐渐在一些小的试点项目中生产环境引用G1来验证是否可以解决问题以及可能会引入的风险。
预备知识
垃圾回收首先要判断一个对象是不是垃圾,Java里不用引用计数器算法,都是用从GC root开始的可达性分析算法,在实际实现的时候就是标记。所以不管是什么新生代、老年代回收,都有标记的步骤。因为目前市面上能见到的版本都是从分代垃圾收集器开始的,所以更原始的这里就不再提了。
上图中的Serial、ParNew、Parallel Scavenge都是年轻代算法,CMS、Serial Old、Parallel Old是老年代算法。直接连接的线之间才可以配合使用。一般年轻代和老年代的总空间比例是1:2。小的年轻代可以保证更快的进行Young GC。
年轻代算法都是基于复制算法,准确的说是标记-复制算法。因为第一步是要先标记可达对象,然后把可达对象复制到一块空区域,再把原来的区域清空。区别只是Serial是串行的,Serial工作过程中用户线程都是停掉的。ParNew和Paralled Scavenge是并行的。所谓并行是指多个线程同时做垃圾收集的事情,但是仍然是要停下用户线程的工作的。Paralled Scavenge比ParNew的一个优势在于Paralled Scavenge可以设置自适应调节Eden与Survivor区的比例、晋升老年代的比例。
Serial Old老年代算法采用的是标记整理算法,Paralled Old老年代算法采用的也是标记整理算法,不同点只是一个是完全串行的,Paralled Old垃圾回收的时候有多个线程来跑,但是不可以跟用户线程一起跑。但是不管年轻代、老年代,以及目前市面上的所有算法都不能避免STW(stop the world停止用户线程)。
CMS是Concurrent Mark Sweep的缩写,就是并发标记清除算法,它与其他两种老年代算法不同的是它是只标记清除,不整理。目标是减少STW。上面的图中标记了CMS不能配合Paralled Scavenge使用,只能用ParNew。大家想想为啥咧。
上面说了Paralled Scavenge的优势在于可以自动调节。而CMS是只清除操作,不整理。这种算法没有办法应对空间的变化。我看到的文章都没有对它们为何不能配合使用做解释。所以这里强调下。
CMS过程分为下面4步:
上面4步中,初始标记和重新标记其实是一个东西执行两次,就是为了避免在并发标记过程中对象关系有变化。通常来讲STW引用线程的停顿时间:
Serial Old > Paralled Old > CMS。但是CMS有个致命的弱点,CMS必须要在老代码堆内存用尽之前完成垃圾回收,否则会触发担保机制,退化成Serial Old来垃圾回收,这时会造成较大的STW停顿。所以JDK1.8默认的垃圾收集器是Paralled Scavenge+Paralled Old方式。
G1垃圾回收
G1的设计目标是为了替代CMS,它不存在退化为Serial的问题,声称STW时间不超过10ms。主要的特点如下:
在15年16年的时候,很多公司都有使用G1的需求,但是那时候G1由于算法复杂,设计开发困难,所以还不成熟。在17年以后,已经被JDK9选为默认垃圾收集器。注意JDK8的默认垃圾收集器是Paralled Scanvenge,不采用CMS是因为CMS不稳定可能会退化成Serial Old。所以能被选为默认收集器说明它的稳定性是受官方认可的。
G1的原理是分治法,将堆分成若干个等大的区域。优先回收垃圾多的区域。