认识垃圾回收器-G1

237 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 12 天,点击查看活动详情

1 前言

在之前的文章中,已经介绍了 jvm 相关的内容,自从 jdk8 广泛使用以来,分代垃圾回收器就逐渐的剖析开来,最近看了 java 虚拟机 3,其中新增了分区垃圾回收器,现在的垃圾回收器已经从原来的 6 个增加到了目前的 10 个,从本文开始就开始从 G1 垃圾回收器开始讲解。

2 G1 垃圾回收器

G1 垃圾回收器即 Garbage First, G1 可以说是 CMS 垃圾回收器的继承者,都是为了减少 STW 的停顿时间。 G1 保留了分代的概念,也开创了分区的先河,开创了基于 Region 的堆内存布局,将堆内存划分为不同的区域,扮演着 Eden、Survivor 和 Old 空间,但是 G1 还有一种特殊的 Humongous 区域,专门用来存储大对象,每个 Region 的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围为 1MB-32MB,且应为 2 的 N 次幂。

# 设置 Region 的大小
-XX:G1HeapRegionSize
# 启动 G1 垃圾回收器
-XX:+UseConcMarkSweepGC
# G1 默认停顿时间 200毫秒
-XX:MaxGCPauseMillis

虽然 G1 保留了新生代和老年代的概念,但是其空间已经不是固定的了,G1 垃圾回收器将 Region 作为垃圾回收器的最小单元,建立起了可以预测的 STW 时间模型,这样就可以避免整个 java 堆的全区域垃圾回收,Garbage First 即是以垃圾为第一目标,优先处理回收价值最大的区域 ,这就是 G1 的名称由来。下图即是垃圾回收器的 Region 分区示意图:

G1 就是将堆内存化整为零,但是引发了另外一个问题, Region 里面存在的跨 Region 对象怎么解决呢? G1 会维护一个记忆集,存储的内容就是一个 hash 表,这种数据结构是一个双向卡表结构,记录了改区域指向了那个区域,以及哪个区域指向了自己。因为多了一些维护指向关系的卡表结构的存在,因此 G1 垃圾回收器比传统的分代回收器占用更多的内存,通常来讲会耗费 10%-20% 的额外内存来维持垃圾回收器的工作。我们都知道 CMS 垃圾回收器使用 Incremental Update 即增量更新的方式来解决并发的问题,因此会产生漏标的问题。G1 正式通过原始快照 STAB 的方式来实现,G1 则是通过维护记忆集表来解决并发的问题,因此会耗费额外的空间。

3 G1 的垃圾回收步骤

G1 通过三色标记法来标记垃圾,黑色即和根对象相连,灰色表示该对象被扫描过,但是子对象没有扫描完成,白色表示该对象没有扫描完成,的垃圾回收步骤分为四个步骤:

  • 1 初始标记 初始标记仅仅是标记一下 GC Roots 能关联到的对象,并且修改 TAMS 的值,这个阶段需要停顿线程,但是耗时很短,而且可以和 Minor GC 同时运行。
  • 2 并发标记 从 GC Roots 开始对堆中的对象进行可达性分析,递归扫描整个堆中的对象图,找出需要回收的对象,这个过程可以并发运行,但是耗时比较长,处理完成后还要处理 STAB 后有变动的引用对象。
  • 3 最终标记 对用户线程做一个短暂的停顿,用于处理并发阶段结束后仍遗留下来的 STAB 记录。
  • 4 筛选回收 统计每个 Region 的数据,对每个 Region 的回收成本和价值进行排序,来定制具体的回收计划。

4 总结

在本文中,简单讲述了 G1 垃圾回收器的一些知识,主要是 STAB 和 Region 的概念,但是 G1 并不是垃圾回收器的终点,还会有更加优秀的垃圾回收期等待了解。