G1 垃圾收集器
与 C、C++等手动管理内存的编程语言不同,Java 程序在运行时会自动对内存中不再使用的对象进行检测,并回收这部分对象所占用的内存空间。
在 JDK 中,JVM 将堆(heap)内存分为了不同的区域块,每种区域会使用不同的垃圾收集器,每种垃圾收集器又会使用各自的垃圾收集算法。因此 JVM 的垃圾收集设计大致架构如下,从上至下越来越偏向于细节,本文使用自底向上的方式进行介绍
- 堆内存
- 堆内存分为多个区域
- 每个区域使用了不同的垃圾收集器
- 每种垃圾收集器使用了一个或多个垃圾收集算法
垃圾收集算法
检测垃圾的方式一般有引用计数法和可达性分析两种,因为引用计数法无法检测到循环引用的垃圾对象,所以现在 JVM 中进行垃圾检测都是使用的可达性分析方法。
在检测到了垃圾对象之后,JVM 就需要对这些垃圾对象进行垃圾回收(英语:Garbage Collection,缩写为 GC),JVM 中使用到的GC 算法主要由如下三种
标记-清除算法(Mark-Sweep)
该算法分为标记和清除两个步骤,第一步标记是将所有活动的对象做上标记,第二部清除是将所有没有标记的对象进行回收。
标记-压缩算法(Mark-Compact)
标记压缩算法和标记清除算法基本上一致,只是标记清除算法过程只能单纯的把对象内存空间给释放出来,时间长了会造成内存碎片的问题。标记压缩算法在标记清除算法的基础上会对存活的对象进行移动,之后把这些对象向某一端进行移动来保证有完整的内存空闲空间。
标记压缩算法避免了内存碎片的问题,内存的利用率得到了提高,但是因为要移动对象所以它的 GC 效率要比标记清除算法要差。
复制算法(Copying)
复制算法将内存分为 FROM 和 TO 两块区域,对象在 From 区域内进行分配,之后对 FROM 区域进行 GC,存活的对象被全部复制 TO 区域;之后对象在 TO 区域进行分配,GC 发生在 TO 区域,GC 后存活的对象被复制到 FROM 区域,如此反复。
垃圾收集器
在 JVM 中存在着多种垃圾收集器,这些垃圾收集器使用了一种或多种垃圾收集算法,我们可以认为垃圾收集算法是垃圾收集器的理论基础。
下面是 JVM 中几种常见的垃圾回收器
| 垃圾回收器 | 垃圾回收算法 |
|---|---|
| Serial | 复制算法 |
| ParNew | 复制算法 |
| Serial Old | 标记-整理 |
| CMS | 标记-清除 |
| G1 | 标记-整理+复制算法 |
经典的堆内存布局(与 G1 做区分)
堆是区别于栈、方法区等等的一大块内存空间,用于存放对象。JVM 将堆内存再进行分区,分为新生代(Young Generation)和老年代(Old Generation),在新生代进行的 GC 叫做 Young GC 或 Minor GC,在老年代进行的 GC 叫做 Old GC 或 Major GC,其中新生代的内存又可以分为伊甸(Eden)和存活区(Survivor),Survivor 分为两块,一般称为 From 和 To。典型的对象分配和 GC 流程如下
- JVM 将对象分配在新生代的 Eden 区域
- 当 Eden 中的对象内存占用到一定的阈值时,触发一次 Young GC
- JVM 将 Eden 中存活的对象复制到 Survivor 的 To 区域,此时 Eden 从逻辑上已经被清空
- 将 Survivor 的 From 和 To 区域的名字进行对调,即 To(此时包含上一次 GC 的存活对象)改为 From,From 改为 To
- JVM 继续将对象分配在 Eden
- Eden 中的对象到达一定的阈值之后触发一次 Young GC,该次 gc 发生在 Eden 和 Survivor 的 From(包含上一次 GC 的存活对象)这两个区中,JVM 将 Eden 和 From 区中的存活对象复制到 To 区
- 继续进行 From 和 To 区的名字对调
- JVM 继续向 Eden 分配对象,下次的 GC 会对 Eden 和 From 区中的对象进行,存活对象被复制到 To 区
- 如此往复,每次的 GC 存活对象都被保存到 Survivor 的 To,随后 To 重新改名为 From、From 也改名为 To,之后等待进行下一次的 GC
上面介绍的是新生代的 GC 过程,当 To 区域存活的对象到达一定的年纪之后(每次 GC 的存活对象都会增加一岁),JVM 就会将该对象移动到 Old Generation。老年代的内存分配相较于新生代要简单的多,只有一块区域。
以 JDK1.8 中流行的 -XX:+UseConcMarkSweepGC 选项为例,该选项会在新生代使用 ParNew 垃圾收集器,在老年代使用 CMS 垃圾收集器。
G1 垃圾收集器堆内存布局
与经典的堆内存不一样,G1 垃圾收集器会将堆内存分为一个个的 region 区域,使用多个 region 来替代以前连续的堆内存空间。内存分块这种思想与 Linux 中的内存分页十分类似,本质都是把内存打散以方便进行管理,用离散的内存来替代连续的内存。内存分块之后可以从根本上解决内存碎片的问题,并且内存管理起来更加的简单。
G1 的 GC 可以分为 Young Only Phase、Mixed gc Phase 和 Full gc Phase。
Young Only Phase
对象会不断地分配到 Eden 分区中,Eden 分区的数量会不断地增长,这个操作会一直进行下去直到 Eden 分区数量达到上限。
当 Eden 分区的数量达到了阈值之后,就会对所有的 Eden 分区进行 GC,之后存活的对象会移动到 Survivor 区中,如果没有 Survivor 区则会随意挑选一个空闲的 region 作为 Survivor 并保存存活对象。
同时如果 Survivor 分区中的某些对象达到了一定年纪之后,这些对象也会被复制到 Old 分区中。
Mixed gc Phase
Old 分区越来越大,当老年分区的占比达到了一定比例之后,就会触发针对年轻代和老年代的 GC,是为 Mixed gc。
在进行 Mixed gc 之前,先需要进行**并发标记周期 (Concurrent Marking Cycle)**,这个过程会分为 5 步
- 初始标记(Young Collection with Initial Mark),它伴随着一次普通的 Young GC 发生,然后对 Survivor 区进行标记,因为该区可能存在对老年代的引用,会触发 Stop The World
- 根区间扫描,因为先进行了一次 YGC,所以当前年轻代只有 Survivor 区有存活对象,它被称为根引用区(root region)。扫描 Survivor 到老年代的引用,该阶段必须在下一次 Young GC 发生前结束
- 并发标记(Concurrent Marking),寻找整个堆的存活对象,该阶段可以被 Young GC 中断
- 重新标记(Remark),完成最后的存活对象标记。使用了比 CMS 收集器更加高效的 snapshot-at-the-beginning (SATB) 算法,会触发 Stop The World
- 清除(Clean),清理阶段真正回收的内存很少
到这里,G1 的一个并发周期就算结束了,其实就是主要完成了垃圾定位的工作,定位出了哪些分区是垃圾最多的。因为整堆一般比较大,所以这个周期应该会比较长,中间可能会被多次 STW 的 Young GC 打断。
等到并发标记周期完成之后,就会进入 Mixed gc Phase 了,混合垃圾收集周期既会回收新生代的垃圾,也会回收老年代的垃圾。
Full gc Phase
当以下两个现象同时发生时就会触发 Full GC
- 空闲分区不足或巨型对象无法在老年代找到连续的分区,此时 JVM 报错 to-space exhausted。
- 在现象 1 发生时,G1 会尝试增加堆的使用量,如果增加失败则会触发 Full gc
Full gc 时单个线程会对整个堆的所有代中所有分区做标记、清除以及压缩动作,非常昂贵。
可以通过对 JVM 设置如下的 Xlog 来打印 GC 相关的日志以方便对 JVM 的 GC 过程进行深入的了解:
-Xlog:gc*:file=logs/gc.log:t,tags,level:filecount=5,filesize=100m
G1 在 Elasticsearch 中的使用
Elasticsearch 对 JDK 8u40 之前的 Java 版本是 不推荐使用 G1 垃圾收集器的,如果检测到 错误的 Java 版本和 GC 配置,Elasticsearch 会启动失败。但是在 Elasticsearch 7.5.2 自带的版本为 13.0.1 的 bundled JDK 中,G1 已经成为了默认的垃圾收集器,G1 已经通过了 ES 的测试验证 并且 ES 团队也给出了一些关于 Java 堆 (heap) 内存管理的 最佳实践。
参考
咱们从头到尾说一次 Java 垃圾回收 - InfoQ
JVM 中的垃圾回收策略
搞懂 G1 垃圾收集器 - 博客园
G1GC 概念与性能调优 - OPPO 互联网技术
G1 垃圾收集器介绍 - Javadoop
Universal GC Log Analyzer
Java Hotspot G1 GC 的一些关键技术 - 美团技术团队
新一代垃圾回收器 ZGC 的探索与实践 - 美团技术团队
Getting Started with the G1 Garbage Collector
Go 垃圾回收(三)——三色标记法是什么鬼?
V8 增量 GC 之三色标记
Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide
jdk11 下 g1 收集器使用