什么时候应该用CMS,而不是G1

发布时间 2023-03-25 00:17:40作者: haste

这个问题是我最近有点吃饱撑的,才想出来的这个问题;

首先要说明,在绝大多数(99.9)的情况下,我们在使用Java8的时候,直接用g1替换掉cms是没问题的,尤其是我们的管理的内存超过了16G,而且对停顿时间有低于10ms的需求。毕竟:

这种情况下不能用zgc,因为Java8还没有支持(上次面试官问我为什么没有用的时候完全想不起来这个原因,囧rz)

首先,我们说一下为什么G1比CMS好,CMS的三个明显的缺陷:

1.对处理器资源敏感,CMS既然姓Concurrent,那么会因为并发特性,占用处理器的资源。CMS默认启动的回收线程数是【(处理器核心数+3)/4】,也就是说,当我们核心数是四个(大多数标准核心数),那么会有很大一部分计算资源会被浪费

2.无法处理浮动垃圾

浮动垃圾的定义:在CMS的并发标记和并发清理阶段,用户线程是还在继续运行的,程序在运行自然就还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束以后,CMS无法在当次收集中处理掉它们,只好留待下一次垃圾收集时再清理掉。
--《深入浅出Java虚拟机》 周志明

3.会产生碎片,CMS作为一款基于“标记-清除”算法实现的处理器,会有大量的不连续空间产生,为下次大对象的分配带来困难,这种情况下,自然需要触发一次FULL-GC

在G1之前,垃圾收集器要么目标范围年轻代的Minor GC,要么针对老年代的Major GC,要么就是整个Java堆的Full GC。而G1不同,G1着眼于整个堆,衡量标准不再是哪个代,而是哪块内存中的垃圾比较多,回收的效益会比较高,也就是Mixed GC。也就是说,虽然G1仍然遵循分代收集的理论进行设计,但是把整个内存分成了大小相等的的独立区域(region),每一个region都可以根据需要,扮演新生代的Eden空间,Survivor空间,或者老年代空间,并且新增了一个Humongous区,为那些非常大的对象专门使用。

相比于CMS,G1的优势可以说非常显著:标记整理的做法,可以提供规整可用的内存空间,不容易因为需要给大对象分配的空间不足而触发Full GC,用户可以指定一个期望的停顿时间,让g1 去操心这个问题而不是没日没夜的去调整各种jvm参数,对于那种迭代非常快的项目来说优势显著。

但是,我这篇文章的目的,其实是想寻找一下,什么情况下,用CMS可能更好,在周志明的那本书里面提到了几点(以下内容请尽量寻找原文阅读):

  1. 无论是为了内存收集,产生的内存占用(Footprint),还是额外的负载(overload),都比cms高;
    1. 内存占用:都是使用卡表处理跨代指针(待解释),但是CMS只需要一份卡表,而G1则是么个region都需要一份卡表。这就导致G1的记忆集可能占到整个堆的20%,或者更多。
    2. 额外的负载:CMS和G1 都用到了写屏障,CMS用写后屏障更新维护卡表,而G1除此之外,为了实现快照搜索算法,需要写前屏障来跟踪并发的指针变化情况。虽然避免了类似在CMS在最终标记阶段长时间停顿,但是会在用户线程运行时带来额外的负担;

除了周志明的书,在ES的官网,我也找到了不要乱改jvm参数的提示:

Don’t Touch These Settings! | Elasticsearch: The Definitive Guide [2.x] | Elastic

在这里,ES官方给出继续用cms的理由引用如下:

Do not change the default garbage collector!

The default GC for Elasticsearch is Concurrent-Mark and Sweep (CMS). This GC runs concurrently with the execution of the application so that it can minimize pauses. It does, however, have two stop-the-world phases. It also has trouble collecting large heaps.

Despite these downsides, it is currently the best GC for low-latency server software like Elasticsearch. The official recommendation is to use CMS.

There is a newer GC called the Garbage First GC (G1GC). This newer GC is designed to minimize pausing even more than CMS, and operate on large heaps. It works by dividing the heap into regions and predicting which regions contain the most reclaimable space. By collecting those regions first (garbage first), it can minimize pauses and operate on very large heaps.

Sounds great! Unfortunately, G1GC is still new, and fresh bugs are found routinely. These bugs are usually of the segfault variety, and will cause hard crashes. The Lucene test suite is brutal on GC algorithms, and it seems that G1GC hasn’t had the kinks worked out yet.

We would like to recommend G1GC someday, but for now, it is simply not stable enough to meet the demands of Elasticsearch and Lucene.

官方的理由简单来说,G1是新的,有很多bug在爆出,其次,ES基于的lucene 的测试包对GC比较严格,结果是G1GC可能会导致一些奇怪的问题。

当然,这是版本的原因,2023年了,很多问题已经修复,然而如果你的版本还是比较老,内存也不是很大,那么用默认的CMS可能更好些。虽然我觉得: