天天看点

JVM 垃圾收集器 G1 详解

G1 垃圾收集器

Opening words

这是一篇欠了自己很久的文章吧,一直想写一篇关于垃圾收集器 G1 的总结文章,却迟迟没有下笔哈哈,本篇将深入浅出带大家看看 G1 究竟给我们程序的垃圾收集带来了什么。

G1 简介

The Garbage-First (G1) 是服务器类型的垃圾收集器,它适用于大内存的垃圾收集处理并且可以控制垃圾收集 STW 暂停时间长度(当然这个时间的设置需要是合理的),在 JDK 1.7 版本 G1 得到了完全的支持。

G1 通过将活动对象从将一组或者多组区域(该区域称为 集合集 CSet) 增量并行复制到一个或者多个不同的新的区域来实现空间压缩来减少堆内存的碎片。目标是回收尽可能多的内存空间,所以在垃圾回收时 G1 会在满足暂停时间期望的前提下优先挑选垃圾对象最多的区域进行回收。

G1 的一些特性:

  1. 可以和应用程序线程并发运行就像 CMS 垃圾收集器一样。
  2. 紧凑的可使用空间,无碎片化内存。
  3. 可预测的 GC STW 时间。
  4. 高性能

G1 是计划作为 CMS 的替代或者说升级的,通常来说我们都会对 G1 和 CMS 来进行比较,这样可以更好的看出我们使用 G1 的理由。

一个区别是 G1 是压缩收集器,其使用 Region 区域来分配空间,使用标记复制算法来进行垃圾的收集,避免了潜在的内存碎片化问题。

同时 G1 提供了可预测的垃圾收集暂停时长,允许用户设置暂停目标,需要知道的是暂停目标并不是总能达到其是一个预估值,需要进行合理的设置。

内存划分

JVM 垃圾收集器 G1 详解

Region 区, G1 将 Java 堆划分为多个 Region 区,JVM 最多可以有 2048 个 Region 区,一般 Region 区大小为堆大小除以 2048,即 4096M 堆大小,Region 区大小为 2M。可以通过参数

-XX:G1HeapRegionSize

来指定 Region 大小,但推荐使用默认的划分方式。

G1 仍然保留了 CMS 的分代的概念,但是各代之间因为 Region 区不再物理隔离,即当前的 Region 区是 Eden ,在经历垃圾回收后可能会变成其他区如 Old 或者 Survivor 等。

增加了 Humongous 巨型对象区的概念,当一个对象超过了 Region 区大小的 50% 则被认定为巨型对象,直接进入 Humongous 区,在 young gc 时 Humongous 区对象不参与回收,mixed gc 时会对该区域进行回收,以此减少巨型对象的回收频率。

Eden 区 新生代的大小初始默认为整个堆大小的 5% ,在 JVM 运行的过程中会进行动态扩充,但其最大不会超过堆大小的 60%。可以通过参数

-XX:G1NewSizePercent=5

-XX:G1MaxNewSizePercent=60

调整该值,非必要不建议修改。

Humongous 文档

垃圾收集阶段

除了并发失败所导致的 FULL GC 即停止应用线程所有操作,专注于垃圾收集外,G1 分以下两种常规 GC 收集模式

Young Garbage Collections 年轻代垃圾收集

G1 的垃圾收集满足了在 eden 区进行内存分配的请求,young gc 并不是在 eden 区满时就马上触发,G1 会计算评估当前收集垃圾所消耗的时间,如果该时间远远小于设置的

-XX:MaxGCPauseMillis

最大停顿时间,G1 将尝试扩充 eden 区域,直至某次计算接近该值则触发 gc。

在 young gc 的过程中会同时清理前一次垃圾收集后形成的 eden 区和 survivor 区,在 eden 区和 survivor 的幸存者即非垃圾对象将被复制或者疏散到新的区域,分配到的目的区域取决于对象的年龄(这就和 CMS 一样),那些足够老化的对象将被分配到老年代,否则其将被移到 survivor 区并被添加到下一次垃圾回收的 CSet 中,其余垃圾对象将被直接清除(可达性分析)。

Mixed Garbage Collections 混合垃圾收集

在成功完成并发标记周期后,如果发现 young gc 无法回收出空余内存 G1 将切换到 mixed 混合垃圾收集方式,在此过程 G1 可以选择将 old 区添加到将要收集的 eden 和 survivor 区域集合中。G1 在收集到足够数量的 old 区域后(多次 mixed gc 后)会恢复为 young gc 。

标记周期阶段

  1. 初始标记 Initial marking phase【STW】:初始标记会标记根引用,并装载正常的 young gc。该阶段为 STW。
  2. 根区域扫描 Root region scanning phase:G1 垃圾收集器扫描在初始标记阶段标记的 survivor 区域,以获取老年代的引用,并标记引用的对象。此阶段与应用程序同时进行。
  3. 并发标记 Concurrent marking phase:在整个堆中找到可达对象。并发标记阶段就是从 GC Roots 的直接关联对象开始遍历整个对象图的过程, 这个过程耗时较长但是不需要停顿应用线程, 可以与垃圾收集线程一起并发运行。因为并行可能会有导致已经标记过的对象状态发生改变。因而需要重新标记
  4. 重新标记 Remark phase【STW】:该阶段将 STW 来帮助最后完成标记,该阶段将使用 **原始快照(Snapshot At The Beginning,SATB) ** SATB buffers 追踪未扫描对象并进行标记处理。
  5. 清理阶段 Cleanup phase【STW】:在这个最后阶段 G1 将进行 STW 并清洗 RSet,G1 将根据设置的目标暂停时间来制定清理计划,选择回收效益最高的区域进行回收。回收算法采用复制算法。此过程将标记出完全空闲的区域,并且将判断是否需要切换为 mixed gc
新生代可以引入记录集(Remember Set)的数据结构(记录从非收集区到收集区的指针集合),避免把整个老年代加入GCRoots扫描范围。
JVM 垃圾收集器 G1 详解

G1 的 Full GC

当 mixed gc 失败后将触发 Concurrent Mode Failure 并发失败,执行 full gc,该过程将启用单线程执行垃圾收集,暂停所有应用线程。

重要的配置

Option and Default Value Option
-XX:G1HeapRegionSize=n 设置 G1 region 区的大小,大小在 1MB 到 32MB 。值应该是 2 的 n 次幂,基于最小 java 堆大小最多划分 2048 个 region 区。
-XX:MaxGCPauseMillis=200 最大 GC 停顿时间 单位毫秒
-XX:G1NewSizePercent=5 年轻代最小值,占总堆大小的百分比,默认为 5%
-XX:G1MaxNewSizePercent=60 年轻代最大值,占总堆大小的百分比,默认为 60%
-XX:ParallelGCThreads=n 设置 STW 工作线程的值。该值设置默认为逻辑处理器的数量。最多为 8。如果有八个以上的逻辑处理器,请将 n 的值设置为逻辑处理器的大约 5/8。这在大多数情况下都有效,但较大的 SPARC 系统除外,其中 n 的值大约为逻辑处理器的 5/16。
-XX:ConcGCThreads=n 设置并行标记线程的数量。设置

n

为并行垃圾回收线程 (ParallelGCThreads) 数量的大约 1/4。
-XX:InitiatingHeapOccupancyPercent=45 设置触发标记周期的 Java 堆占用阈值。默认占用率为整个 Java 堆的 45%。
-XX:G1MixedGCLiveThresholdPercent=85 设置要包含在混合垃圾回收周期中的旧区域的占用阈值。默认占用率为 85%。
-XX:G1HeapWastePercent=5 设置愿意浪费的堆百分比。当可回收百分比小于堆浪费百分比时,Java HotSpot VM 不会启动混合垃圾收集周期。默认值为 5%。
-XX:G1MixedGCCountTarget=8 在标记周期后设置混合垃圾收集的目标数量,以收集最多具有

G1MixedGCLIveThresholdPercent

实时数据的旧区域。默认为 8 个混合垃圾回收。混合集合的目标是在此目标数量内。
-XX:G1OldCSetRegionThresholdPercent=10 设置混合垃圾收集周期中要收集的旧区域数量的上限。默认值为 Java 堆的 10%。
-XX:G1ReservePercent=10 设置保留内存的百分比以保持空闲状态,以降低空间溢出的风险。默认值为 10%。当您增加或减少百分比时,请确保将总 Java 堆调整相同的数量。