G1垃圾收集器相比于CMS有哪些关键优势?它的Region划分和RSet设计起到了什么作用?
G1(Garbage-First)垃圾收集器是HotSpot JVM中一个重要的并发、并行、增量、分代、整理(compacting)收集器,它设计的目标是取代CMS(Concurrent Mark-Sweep)收集器,提供更好的可预测的暂停时间。
G1相比于CMS的关键优势
-
可预测的停顿时间目标 (Pause Time Goal):
- G1最大的优势在于它允许用户指定一个软实时(Soft Real-Time)的停顿时间目标(通过
–XX:MaxGCPauseMillis参数),例如200毫秒。G1会尽量在用户设定的停顿时间内完成垃圾回收工作。 - CMS虽然也有并发阶段,但在某些阶段(如初始标记、重新标记)仍会发生STW(Stop-The-World),且这些停顿时间有时难以预测,可能因为老年代碎片化或并发模式失败(Concurrent Mode Failure)而导致长时间停顿。
- G1最大的优势在于它允许用户指定一个软实时(Soft Real-Time)的停顿时间目标(通过
-
避免内存碎片问题:
- CMS是一个“标记-清除”算法的收集器,在回收完成后不进行内存整理,容易产生大量的内存碎片。当需要分配大对象时,如果没有连续的足够大的空间,就会提前触发Full GC,导致长时间停顿。
- G1是一个“标记-整理”算法的收集器。在执行Young GC和Mixed GC时,它会通过将存活对象从一个Region拷贝到另一个空的Region来实现“整理”,从而有效地避免了内存碎片问题,这对于长时间运行的应用程序尤为重要。
-
更高效的堆空间利用:
- CMS主要关注老年代的回收,并且在并发标记阶段需要预留一部分空间供应用程序继续运行。
- G1可以同时管理年轻代和老年代,并在Mixed GC阶段选择性地回收部分老年代Region,优先回收垃圾最多的Region(这也是“Garbage First”的由来),从而更有效地利用堆空间。
-
处理大对象 (Humongous Objects):
- G1专门设计了“Humongous Region”来存放大于 Region 大小一半的巨型对象,并且能够更好地处理这些巨型对象的分配和回收,避免它们被提前提升到老年代,减少了内存碎片。
- CMS处理巨型对象时可能会遇到挑战,例如分配失败或导致过多的Full GC。
-
并发和并行性:
- 两者都有并发(与应用线程并行)和并行(多个GC线程并行)阶段。
- G1的并发标记阶段与CMS类似,但G1的“拷贝-整理”过程可以在多核CPU上并行执行,提高了效率。
G1的Region划分和RSet设计的作用
G1的核心创新在于其独特的堆内存布局——Region划分,以及为了支持这种划分而引入的RSet(Remembered Set)设计。
Region划分的作用:
G1将整个Java堆内存划分为多个大小相等的独立区域(Region),每个Region的大小在JVM启动时确定(通常为1MB到32MB,且是2的幂)。这些Region在逻辑上可以承担不同的角色:
- 灵活的分代管理: G1不再有固定大小的年轻代和老年代,而是由一组Region动态地组成年轻代(Eden和Survivor)和老年代。年轻代的大小会根据G1的GC情况动态调整。
- 增量式GC(Incremental GC): 这是Region划分带来的最重要优势。G1的GC不再是对整个堆进行回收,而是选择性地对部分Region进行回收。在一次GC暂停中,G1会根据设置的停顿时间目标,选择回收价值最高(即垃圾最多)的Region进行收集。这种“分而治之”的策略使得每次GC的停顿时间更短、更可控。
- 局部性原理: 将对象分配到独立的Region中,有助于提高缓存的局部性。
- 空间整合 (Compaction): 当一个Region被回收时,G1会将其中存活的对象复制到另外一个或多个空闲Region中,从而有效地实现了内存整理,避免了碎片问题。
RSet(Remembered Set)设计的作用:
由于G1是增量式收集,它在GC某个Region时,不能扫描整个堆来查找指向该Region中对象的引用。RSet就是为了解决这个问题而设计的。
- RSet的定义: 每个Region都维护一个RSet。一个Region的RSet记录了从堆中其他Region指向该Region的所有引用。简单来说,RSet是用来记录“外部指向内部”的指针。
- 如何维护RSet: G1通过“写屏障”(Write Barrier)技术来维护RSet。当应用程序修改了一个引用,例如
objectA.field = objectB时,写屏障会被触发。如果objectA和objectB位于不同的Region,并且这个引用是从objectA的Region指向objectB的Region,那么就会在objectB的RSet中记录这个引用信息。 - RSet在GC中的作用:
- 精确的跨代引用扫描: 当G1进行垃圾回收时,它只需要扫描被收集Region内部的引用,以及该Region的RSet中所记录的来自其他Region的引用。这样,GC线程就不需要扫描整个堆来找到所有根对象,大大缩小了扫描范围,提高了GC效率。
- 支持并发标记: RSet的维护是并发进行的,它允许G1在应用线程运行的同时,收集器线程能够独立地进行标记和回收,而不需要长时间的STW。
- 实现增量式收集的关键: RSet使得G1可以独立地收集部分Region,而无需考虑整个堆的连通性,这是实现G1“增量式”和“可预测停顿”目标的关键基础。
总结来说,G1通过Region划分实现了堆的逻辑分块,支持增量式回收和内存整理;而RSet设计则解决了增量式回收中跨Region引用的追踪问题,使得G1能够在不扫描整个堆的情况下,高效、准确地收集指定Region的垃圾,从而提供了比CMS更优越的可预测停顿时间和更低的内存碎片。