美文网首页
[翻译]Part 1:G1垃圾收集器简介

[翻译]Part 1:G1垃圾收集器简介

作者: 徐士林 | 来源:发表于2018-02-07 18:46 被阅读29次

原文,侵删

对于大多数人来说,Java垃圾收集器是一个黑匣子。程序员开发一个应用程序,QE验证功能,运营团队部署它。在这个过程中,你可能会对整个堆,PermGen / Metaspace或线程设置进行一些调整,但除此之外,程序还是正常工作。The question then becomes, what happens when you start pushing the envelope?当这些默认设置不再满足时会发生什么?对于开发人员,测试人员,性能工程师或架构师来说,了解垃圾收集基本工作原理、学会收集分析相应数据并将其有效的应用在调优实战中是一项非常难宝贵的技能。在本文的的系列中,我们将带您加入理解G1垃圾收集器的旅程,并将您对G1的理解从初学者转化为爱好者。将GC置于您的性能之巅。

我们以最基本的主题来开始这个系列:G1(垃圾优先)收集器有什么重要意义,它是如何工作的?如果对G1的目标、如何作出决策以及如何设计没有总体了解的话,那么你将寸步难行(you are setting out to achieve a desired end state with no vehicle or map to get you there.)

G1收集器的目标是通过 -XX:MaxGCPauseMillis来实现可预测的非强制暂停时间,同时还保持应用程序吞吐量。The catch and ultimate goal is to be able to maintain those targets with the present day demands of high-powered, multi-threaded applications with an appetite for continually larger heap sizes.G1收集器有一个一般的规则,暂停时间目标越高,可达到的吞吐量和总延迟就越高。暂停时间目标越低,可达到的吞吐量和总延迟就越低。垃圾收集的目标是结合对应用程序运行时需求的理解,应用程序的物理特性以及对G1的了解来调整一组选项,并实现满足业务需求的最佳运行状态。重要的是要记住,调优是一个不断发展的过程,通过重复测试和评估,您可以建立一套基准和最佳设置。没有权威指南或者一套魔法选项可以使用所有情况。在程序达到最佳性能之前,你都有责任进行性能评估,参数修改然后在进行性能评估。

G1的工作就是用几种不同的方式完成这些目标。首先,就像G1的名字一样,它首先收集数据量最少的区域(垃圾优先),并将数据压缩/疏散到新的区域。其次,G1采用一系列增量式,并发式和多周期的方式来实现soft pause time target。这允许G1在定义的时间内完成工作,而不管整个堆的大小。

在上面,我们提到了G1中的一个名词:"区域",简而言之,一个区域代表一个可分配的空间块,这样的一个区域可以容纳任何一代的对象,而不需要保持与同一代的其他区域的连续性。在G1中,传统的年轻代和老年代仍然存在。年轻代包含eden区和survivor区。所有新分配的对象都会首先尝试在eden中分配空间。而年轻代垃圾收集过程存活的对象会被复制到survivor区,直到该对象被收集或者年龄达到由XX:MaxTenuringThreshold(默认15)指定的值从而晋升到老年代为止当对象达到指定年龄时。可以晋升到老年代。当然,这里也有一个例外,我们将在文章结尾部分讨论这个问题。 区域的大小会在JVM启动时计算和定义。它基于尽可能接近2048个区域的原则,其中每个区域的大小为1到64 MB之间的2的幂。更简单地说,对于一个12 GB的堆:

  • 12288 MB / 2048 Regions = 6 MB - 这不是2的幂
  • 12288 MB / 8 MB = 1536 regions - 区域数相对较少
  • 12288 MB / 4 MB = 3072 regions - 可以接受

基于上面的计算,JVM在启动时默认分配3072个区域,每个区域位4MB。


heap-empty-regions.png

也可以通过-XX:G1HeapRegionSIze参数显式的指定区域大小。在设置区域大小时,重要的是要了解创建的区域数量,因为区域越少,G1的灵活性越差,扫描,标记和收集每个区域的时间越长。在所有情况下,空白区域被添加到一个称为“空闲列表”的无序链接列表中。

虽然G1是分代收集器,但空间的分配和消费是不连续的并且JVM更好的理解了程序后可以调整年轻代和老年代的比例。当开始产生对象时,从空闲列表中分配一个区域作为线程本地分配缓冲区(TLAB),并且使用CAS来实现同步。然后可以在这些线程本地缓冲区内分配对象,而不需要额外的同步。当这个区域空间用尽,一个新的区域会被选中。这一直持续到eden区域被填满,引发疏散停顿(也被称为young collection / young gc / young pause or mixed collection / mixed gc)。eden区域的数量标识我们认为可以在指定的停顿时间内完成的垃圾数据的区域的数量。分配给eden区域的数量可以是整个堆的5%到60%的范围内,并根据之前的年轻代垃圾收集的性能作出调整。

下面是一个例子,它将对象分配到非连续的Eden区域 heap-eden-regions.png
GC pause (young); #1
          [Eden: 612.0M(612.0M)->0.0B(532.0M) Survivors: 0.0B->80.0M Heap: 612.0M(12.0G)->611.7M(12.0G)]
GC pause (young); #2
          [Eden: 532.0M(532.0M)->0.0B(532.0M) Survivors: 80.0M->80.0M Heap: 1143.7M(12.0G)->1143.8M(12.0G)]

根据上面的年轻代GC的日志,可以看到在第一次停顿时,由于Eden在总共612.0M的空间(153个区域)中使用了了612.0M,所以触犯了疏散(evacuatin)。目前Eden的空间已经完全撤离至0.0B,并且考虑到时间的消耗,还决定将Eden的总分配减少到532.0M或133个地区。在第二次暂停,可以看到当我们达到532.0M的新的限制时触发了疏散。因为我们取得了最佳的停顿时间,所以Eden保持在532.0M。

当上述的年轻代GC发生时,不可达的对象会被收集,剩余的活的对象被撤离并压缩到Survivor。G1有一个显式的hard-margin,这保证在撤离过程中,Survivor空间与整个堆空间的比例总是维持在某个固定值,该值由G1ReservePercent,默认是10%。如果没有这么多可用的空间,这个堆可能会填满,从而没有可用的疏散区域。不能保证这不会发生,但这是调优的目的!这一原则确保在每次成功疏散之后,所有以前分配的Eden区域都将返回到"free list",任何可达最终都会留在Survivor区域。

下面是一个标准的年轻代GC的例子: heap-survivor-region.png

继续这种模式,对象再次分配到新分配的Eden区域中。当Eden区被填满时,有一次发生年轻代GC,并且根据现有存活对象的年龄(年龄根据该对象经历过多少次年轻代GC来判断),达到年龄的对象升级到老年代。

下面是一个年轻代GC的例子,当Survivor区的可达对象被提升到老年代的一个新区域时,Eden的可达对象对复制到一个新的Survivor空间区域。由删除线表示的撤离区域现在为空,并返回到空闲列表。


heap-old-region.png

G1将继续这种模式,直到发生三件事情之一:

  1. 达到了通过InitiatingHeapOccupancyPercent(IHOP)设置的soft-margin。
  2. 达到了G1ReservePercent设置的hard-margin
  3. 遇到了一个大对象,

关注主要触发因素,IHOP代表了一个时间点,按照年轻的GC计算,老年代中的对象数量占堆总数的45%(默认)。这个比率不断地被计算和评估作为每个年轻GC的组成部分。当这些触发器中的一个被触发时,请求开始并发标记的循环。

8801.974: [G1Ergonomics (Concurrent Cycles) request concurrent cycle initiation, reason: occupancy higher than threshold, occupancy: 12582912000 bytes, allocation request: 0 bytes, threshold: 12562779330 bytes (45.00 %), source: end of GC]
8804.670: [G1Ergonomics (Concurrent Cycles) initiate concurrent cycle, reason: concurrent cycle initiation requested]
8805.612: [GC concurrent-mark-start]
8820.483: [GC concurrent-mark-end, 14.8711620 secs]

在G1中,并发标记基于snapshot-at-the-beginning(SATB)的原则。这就意味着,为了提高效率,只有在初始快照中存在的对象,才能被识别为垃圾。在并发标记周期中出现的任何新分配的对象被认为是活的而不管它们的真实状态如何。这一点很重要,因为完成标记所花费的时间越长,回收的垃圾就可能变成活的。如果在并发标记期间分配的对象多于最终收集的对象,则最终会耗尽堆空间。在并发标记周期中,你会看到年轻代GC继续,因为它不是一个STW的事件。

下面是一个例子,说明一个年轻代GC到达IHOP的门槛后,触发一个并发标记。


heap-fillingup.png

一旦并发标记周期完成,立即触发年轻代GC,然后是第二种撤离,即混合收集。混合收藏几乎就像一个年轻的收藏,有两个主要的区别。首先,混合收集也将收集,疏散和压缩一组选定的旧地区。其次,混合收藏不是基于年轻收藏使用的相同的疏散触发。他们的目标是尽可能快速和经常地收集信息。他们这样做是为了最大限度地减少软停止目标中选择的旧地区的数量,从而最大限度地减少分配的伊甸园/幸存者地区的数量。

相关文章

  • JVM源码分析系列

    JVM G1算法系列 G1垃圾收集器介绍 G1垃圾收集器之RSet G1垃圾收集器之SATB G1垃圾收集器之对象...

  • G1收集器详解

    详解 JVM Garbage First(G1) 垃圾收集器 G1垃圾收集器入门

  • [翻译]Part 1:G1垃圾收集器简介

    原文,侵删 对于大多数人来说,Java垃圾收集器是一个黑匣子。程序员开发一个应用程序,QE验证功能,运营团队部署它...

  • G1收集器详解

    1.基础介绍 1.1 G1简介 Garbage First(简称G1)收集器是垃圾收集器技术发展历史上的里程碑式的...

  • G1垃圾收集器

    G1垃圾收集器在JDK1.7中投入使用,并作为JDK1.9默认的垃圾收集器。 JVM配置开启G1参数: 一、G1与...

  • G1垃圾收集器,永久带和元数据区

    引用地址 G1垃圾收集器,永久带和元数据区 我们收到了一些围绕G1垃圾收集器和使用永久带的一些问题。当G1作为垃圾...

  • Java G1垃圾回收

    G1 垃圾收集器 Garbage-First (G1) 收集器是一种服务器式垃圾收集器,针对具有大内存的多处理器机...

  • 垃圾回收文章

    深入理解 Java G1 垃圾收集器

  • G1垃圾回收器

    1、背景 G1(Garbage First Collector 垃圾优先的收集器),说是一种全新的,其实G1垃圾收...

  • 23-一文带你搞懂G1收集器

    G1收集器介绍 Garbage First(简称G1) 收集器是垃圾收集器技术发展历史上的里程碑式的成果, 它开创...

网友评论

      本文标题:[翻译]Part 1:G1垃圾收集器简介

      本文链接:https://www.haomeiwen.com/subject/navozxtx.html