0%

Garbage-First(G1)

前面我们介绍了 Serial GCParallel GC 以及 CMS GC,本篇将为你介绍当今收集器技术最前沿的GC —— G1(Garbage-First)。

概述

Garbage-First (G1) 是一款面向服务端的垃圾收集器,支持新生代和老年代空间的垃圾收集,主要针对配备多核处理器及大容量内存的机器。在实现高吞吐量的同时,尽可能的满足垃圾收集暂停时间的要求。G1最主要的设计目标是: 实现可预期及可配置的STW停顿时间。G1为具有以下需求的应用而设计:

  • 能够像CMS收集器一样,能与应用程序线程并发运行
  • 不需要较长的GC停顿时间来整理内存空间
  • 可预测GC的停顿时间
  • 不想要牺牲大量的吞吐性能
  • 不需要更大的Java堆内存

在 Java9 中,G1已取代CMS收集器,成为JVM默认收集器,与CMS相比,G1具有以下优势:

  • G1在压缩空间方面有优势

  • G1通过将内存空间分成若干区域(Region)的方式避免内存碎片问题

  • Eden、Survivor、Old区不再固定,在内存使用效率上来说更灵活

  • G1可以通过设置预期停顿时间(Pause Time)来控制垃圾收集时间避免应用雪崩现象

  • G1在回收内存后会马上同时做合并空闲内存的工作,而CMS默认是在STW(stop the world)的时候做

  • G1会在Young GC中使用,而CMS只能在O区使用

动机

关于为什么要重新设计一个 G1 垃圾收集器,论文 中给出的理由相当简单:现有的垃圾收集器无法满足软实时(Soft Real-time)特性:即让 GC 停顿能大致控制在某个阈值以内,但是又不必像实时系统那样非常严格。这也是很多业务系统都有的诉求。

在过去的 JVM 设计中,堆内存被分割成几个大小都是预先划分好的区域 —— Eden、Survivor、Old 。对于总内存 64GB 的机器,可能 Old 区大小就有 32GB,即使用并行的方式收集一次仍然需要数秒。近十年,随着内存越来越大,这一问题也变得更为严重。

为了达到软实时的目标,同时也是为了更好地应对大内存,G1 将不再使用上述的内存布局。

##堆空间划分

Region

为实现大内存空间的低停顿时间的回收,G1采用了分区(Region)的思路,将整个堆空间分成若干个大小相等的内存区域,物理上不一定连续,逻辑上构成连续的堆地址空间。

因此,在堆的使用上,G1并不要求对象的存储一定是物理上连续的,只要逻辑上连续即可。每个分区也不会确定地为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区。

Humongous

当对象大小超过Region的一半,则认为是巨型对象(Humongous Object,H-Obj),直接被分配到老年代的巨型对象区(Humongous Regions),这些巨型区域是一个连续的区域集,每一个Region中最多有一个巨型对象,巨型对象可以占多个Region。

H-Obj有如下几个特征:

  • H-Obj直接分配到了Old Gen,防止了反复拷贝移动。
  • H-Obj在Global Concurrent Marking阶段的Cleanup 和 Full GC阶段回收。
  • 在分配H-Obj之前先检查是否超过Initiating Heap Occupancy Percentthe marking threshold, 如果超过的话,就启动 Global Concurrent Marking ,为的是提早回收,防止 evacuation failures 和 full GC。

为了减少连续H-Objs分配对GC的影响,需要把大对象变为普通的对象,建议增大Region size。

运作过程

主要参数

参考资料

☕️😊