G1垃圾收集器简述

2022-04-14 09:55:54 浏览数 (1)

魔鬼在细节。——建筑大师

前面的文章对垃圾回收基本原理进行了分析。

现在我们看一下当前流行的G1垃圾回收算法。

G1垃圾收集器是一个多线程垃圾收集器,多线程高并发垃圾收集主要是解决垃圾回收效率问题。(垃圾回收解决三个问题1.回收效率 2.空间碎片 3.full GC问题)

那么G1垃圾收集器有哪些优势特点呢?

1)类似CMS收集器,可以和应用线程同时并发的执行

2)压缩空闲空间时没有GC引起的暂停时间

3)需要更可预言的GC暂停时间

4)不想牺牲大量的吞吐量性能

5)不需要特别大的Java堆

G1垃圾回收器结构

有什么的结构能够支持G1的垃圾回收能够多线程高并发呢。

G1还是沿袭了经典的堆结构,分为年轻代和老年代这样划分的。

G1的堆内存的组织方式有点儿不太一样。

堆内存被划分为多个大小相等的内存块(Region),每个Region是逻辑连续的一段内存,结构如下:

每个Region被标记了E、S、O和H,说明每个Region在运行时都充当了一种角色,其中H是以往算法中没有的,它代表Humongous,这表示这些Region存储的是巨型对象(humongous object,H-obj),当新建对象大小超过Region大小一半时,直接在新的一个或多个连续Region中分配,并标记为H。

G1收集器知道哪个区域基本上是空的。它首先会收集那些产出大量空闲空间的区域。这就是为什么这个垃圾收集的方法叫做垃圾优先的原因。就像名称显示的那样,G1收集器集中它的收集和压缩活动在堆里的那些可完全被回收的区域,那就是垃圾。

G1收集器使用一个暂停预言的模式去达到一个用户定义的暂停时间指标,基于用户指定的暂停时间指标去选择收集区域的数量。

Region堆内存中一个Region的大小可以通过-XX:G1HeapRegionSize参数指定,大小区间只能是1M、2M、4M、8M、16M和32M,总之是2的幂次方,如果G1HeapRegionSize为默认值,则在堆初始化时计算Region的实践大小,具体实现如下:

G1收集器执行一个全局的并发标记阶段来决定堆中的对象的活跃度。

之后标记阶段就完成了。G1收集器知道哪个区域基本上是空的。

它首先会收集那些产出大量空闲空间的区域。

这就是为什么这个垃圾收集的方法叫做垃圾优先的原因。

就像名称显示的那样,G1收集器集中它的收集和压缩活动在堆里的那些可完全被回收的区域,那就是垃圾。

G1收集器使用一个暂停预言的模式去达到一个用户定义的暂停时间指标,基于用户指定的暂停时间指标去选择收集区域的数量。

被G1收集器鉴定为可以回收的区域就是垃圾,使用抽空的方式收集。

G1收集器从堆空间的一个或多个区域里复制对象到堆空间的一个单独的区域内,这个过程中同时压缩和释放内存。这个抽空过程在多处理上以并行的方式运行,以减小暂停时间和增加吞吐量。因此,每一次垃圾收集G1收集器连续不断地去减少碎片,在用户指定的暂停时间内工作。这超越了以往方法的能力。并行年老代(ParallelOld)垃圾收集只进行整个堆的压缩,会导致相当大的暂停时间。

G1收集器不是实时的收集器非常重要。它在很大程度上符合用户设定的暂停时间指标但是并不绝对符合。基于前面垃圾收集的数据来看,G1收集器会估算在用户指定的时间指标能收集多少区域。因此,收集器有一个合理的精确的收集这些区域的代价模型,它使用这个模型决定在用户指定的暂停时间内收集哪些、多少个区域。

G1收集器同时有并发(和应用线程一起运行,比如,提炼、标记、清理)和并行(多线程,比如,stop the world)两个阶段。

全量垃圾回收(Full GC)仍然是单线程的,但是如果调优的适当你的应用应该会避免全量垃圾回收。

G1结构图如下:

图片中的颜色表明了哪个区域被关联上什么角色。活跃对象从一个区域疏散(复制、移动)到另一个区域。区域被设计为并行的方式收集,可以暂停或者不暂停所有的其它用户线程。

明显的区域可以被分配成Eden、Survivor、Old区域。另外,有第四种类型的区域叫做极大区域(Humongous regions)。这些区域被设计成保持标准区域大小的50%或者更大的对象。它们被保存在一个连续的区域集合里。最后,最后一个类型的区域就是堆空间里没有使用的区域。

G1的年轻代

堆空间被分割成大约2000个区域。最小1M,最大32M,蓝色区域保持年老代对象,绿色区域保持年轻代对象。

区域没有必要像旧的收集器一样是保持连续的。

G1的年轻代收集

活跃对象会被疏散(复制、移动)到一个或多个survivor区域。如果达到晋升总阈值,对象会晋升到年老代区域。

这是一个stop the world暂停。为下一次年轻代垃圾回收计算Eden和Survivor的大小。保留审计信息有助于计算大小。类似目标暂停时间的事情会被考虑在内。

这个方法使重调区域大小变得很容易,按需把它们调大或调小。

G1年轻代回收的尾声

活跃对象被疏散到Survivor或者年老代区域。

最近晋升的对象显示为深蓝色。Survivor区域显示为绿色。

关于G1的年轻代回收做以下总结:

  • 堆空间是一块单独的内存空间被分割成多个区域。
  • 年轻代内存是由一组非连续的区域组成。这使得需要重调大小变得容易。
  • 年轻代垃圾回收是stop the world事件,所有应用线程都会因此操作暂停。
  • 年轻代垃圾收集使用多线程并行回收。
  • 活跃对象被复制到新的Survivor区或者年老代区域。

G1年老代垃圾回收

类似CMS收集器,G1收集器为年老代对象被设计成一个低暂停收集器。下面的表描述了在年老代上的G1收集阶段。 G1垃圾收集器在堆上的年老代执行以下阶段。注意一些阶段是年轻代回收的一部分。

阶段

描述

(1)初始标记(stop the world事件)

这是一个stop the world事件,使用G1回收器,背负着一个常规的年轻代收集。标记那些有引用到年老代的对象的survivor区(根区)

(2)根区扫描

为到年老代的引用扫描survivor区,这个发生在应用继续运行时。这个阶段在年轻代收集前必须完成

(3)并发标记

遍历整个堆寻找活跃对象,这个发生在应用运行时,这个阶段可以被年轻代垃圾回收打断。

(4)重新标记(stop the world事件)

完全标记堆中的活跃对象,使用一个叫作snapshot-at-the-beginning(SATB)的比CMS收集器的更快的算法

(5)清理(stop the world事件和并发)

在活跃对象上执行审计操作和释放区域空间(stop the world);净化已记忆集合(stop the world);重置空间区域和返回它们到空闲列表(并发)

(*)复制(stop the world事件)

这些是stop the world暂停为了疏散或者复制活跃对象到新的未使用的区域。这个可以由被记录为[GC Pause (young)]的年轻代区域或者被记录为[GC Pause (mixed)]年轻代和年老代区域完成

循序渐进G1年老代垃圾回收

记住已被定义的阶段,让我们来看一下G1收集器是如何作用于年老代的。

初始标记阶段

年轻代垃圾收集肩负着活跃对象初始标记的任务。在日志文件中被标为GC pause (young)(inital-mark)

并发标记阶段

如果发现空区域(“X”标示的),在重新标记阶段它们会被马上清除掉。当然,决定活性的审计信息也在此时被计算。

重新标记阶段

空的区域被清除和回收掉。所有区域的活性在此时计算。

复制/清理阶段

G1选择活性最低的区域,这些区域能够以最快的速度回收。然后这些区域会在年轻代垃圾回收过程中被回收。在日志中被指示为[GC pause (mixed)]。所以年轻代和年老代在同一时间被回收。

复制/清理阶段之后

被选择的区域已经被回收和压缩到图中显示的深蓝色区和深绿色区中。

年老代垃圾回收总结

总结下,我们可以列出一些关于G1收集器在年老代的上关键点。 并发标记阶段

  • 当应用运行时,并发的计算活性信
  • 在疏散暂停期间,活性信息鉴定哪些区被最好的回收
  • 没有像CMS一样的清除操作

重新标记阶段

  • 使用比在CMS中使用的算法更快的Snapshot-at-the-Beginning(SATB)算法
  • 完全空的区域会被回收掉

复制/清理阶段

  • 年轻代和年老代被同时回收
  • 年老代区域基于它们的活性被选择

命令行选项最佳实践

命令行选项最佳实践

在这部分我们看一下G1收集器的多样的命令行选项。

基本命令行

为了启用G1收集器,使用:-XX: UseG1GC 这个是启动在已下载的JDK演示和示例里的Java2Demo程序的示例命令行: java -Xmx50m -Xms50m -XX:UserG1GC -XX:MaxGCPauseMillis=200 -jar c:javademosdemojfcJava2DJava2demo.jar

关键命令行开关

-XX: UseG1GC - 告诉Java虚拟机使用G1垃圾收集器 -XX:MaxGCPauseMillis=200 - 为最大GC暂停时间设置一个指标。这是一个软目标,Java虚拟机将尽最大努力实现它。因此,暂停时间目标有时候可能不会达到。默认值是200毫秒。 -XX:InitiatingHeapOccupancyPercent=45 - 触发并发垃圾收集周期的整个堆的百分比时机。

最佳实践

使用G1收集器时你应该遵守的一些最佳实践 不要设置年轻代大小 通过-Xmn明确地设置年轻代大小来插手G1收集器的默认行为。

  • 收集时G1收集器将不再遵照暂停时间指标。所以本质上,设置年轻代大小将不会启用暂停时间目标。
  • G1收集器将不能按需扩张、收缩年轻代空间。自从大小被固定之后,大小将不再会被改变。
响应时间指标

代替使用平均响应时间(ART)做为指标,来设置XX:MaxGCPauseMillis=,考虑设置值将会符合这个时间的90%或者更高比例。这意味着90%的用户发出一个请求将不会经历高于这个目标的时间。记住,暂停时间只是一个目标,不保证总是能够达到。

什么是疏散失败?

当Java虚拟机在Survivor和晋升的对象垃圾回收期间,堆空间用光了就会发生晋升失败。堆空间不能再扩展了因为已经在最大值了,使用-XX: PrintGCDetails参数时,这种情况会在GC日志中通过to-space-overflow指示出来。这个代价非常大。

  • 垃圾收集仍然会继续运行,空间必须被释放。
  • 没有成功复制的对象必须就地被提升。
  • 在CSet里的任何到区域的RSets的更新都会重新生成
  • 所有这些步骤代价都非常大
如何避免疏散失败

为了避免疏散失败,考虑以下选项。 增大堆大小

  • 增大-XX:G1ReservePercent=n参数值,默认是10
  • G1收集器创建一个假的上限通过尝试保留储备内存的自由假如’to-space’被渴望得到。 提前启动标记周期 使用-XX:ConcGCThreads=n选项增大标记线程的数量
G1垃圾收集器开关完整列表

这是一个G1垃圾收集器开关的完整列表,记着去使用上述的最佳实践。

选项和默认值

描述

-XX: UseG1GC

使用垃圾优先(G1,Garbage First)收集器

-XX:MaxGCPauseMillis=n

设置垃圾收集暂停时间最大值指标。这是一个软目标,Java虚拟机将尽最大努力实现它

-XX:InitiatingHeapOccupancyPercent=n

触发并发垃圾收集周期的整个堆空间的占用比例。它被垃圾收集使用,用来触发并发垃圾收集周期,基于整个堆的占用情况,不只是一个代上(比如:G1)。0值 表示’do constant GC cycles’。默认是45

-XX:NewRatio=n

年轻代与年老代的大小比例,默认值是2

-XX:SurvivorRatio=n

eden与survivor空间的大小比例,默认值8

-XX:MaxTenuringThreshold=n

最大晋升阈值,默认值15

-XX:ParallerGCThreads=n

设置垃圾收集器并行阶段的线程数量。默认值根据Java虚拟机运行的平台有所变化

-XX:ConcGCThreads=n

并发垃圾收集器使用的线程数量,默认值根据Java虚拟机运行的平台有所变化

-XX:G1ReservePercent=n

为了降低晋升失败机率设置一个假的堆的储备空间的上限大小,默认值是10

-XX:G1HeapRegionSize=n

使用G1收集器,Java堆被细分成一致大小的区域。这设置个体的细分的大小。这个参数的默认值由工学意义上的基于堆的大小决定

G1收集器的垃圾收集日志

G1收集器的垃圾收集日志

我们需要涵盖的最后的主题是使用G1垃圾回收器的日志记录信息来分析性能。这部分提供你可以用来收集打印在日志里的数据和信息的开关的快速的概览。

设置日志详情

你可以设置三种不同级别的详情。 (1)-verbosegc (和-XX: PrintGC等效)参数设置fine日志详情级别 sample Output [GC pause (G1 Humongous Allocation) (young) (initial-mark) 24M- >21M(64M), 0.2349730 secs] [GC pause (G1 Evacuation Pause) (mixed) 66M->21M(236M), 0.1625268 secs]

(2)-XX:PrintGCDetails设置finer详情级别。使用这个选项会显示以下信息: * 显示每个阶段的平均、最小、最大时间 * 根扫描、RSet更新(附带处理的缓冲信息)、RSet扫描、对象复制、终止(附带尝试次数)。 * 也显示’other’时间,比如花费在选择CSet上的时间、引用处理、引用排队和释放CSet。 * 显示Eden、Survivor和总堆空间占用。 Sample Output [Ext Root Scanning (ms): Avg: 1.7 Min: 0.0 Max: 3.7 Diff: 3.7] [Eden: 818M(818M)->0B(714M) Survivors: 0B->104M Heap: 836M(4096M)->409M(4096M)]

(3)-XX: UnlockExperimentalVMOptions -XX:G1LogLevel=finest设置finest详情级别。类似finer但是包括每个工作者线程的信息。 Sample Output [Ext Root Scanning (ms): 2.1 2.4 2.0 0.0 Avg: 1.6 Min: 0.0 Max: 2.4 Diff: 2.3] [Update RS (ms): 0.4 0.2 0.4 0.0 Avg: 0.2 Min: 0.0 Max: 0.4 Diff: 0.4] [Processed Buffers : 5 1 10 0 Sum: 16, Avg: 4, Min: 0, Max: 10, Diff: 10]

决定时间显示格式

有两个开关可以决写在垃圾收集日志中如何显示时间。 (1)-XX: PrintGCTimeStamps - 显示自从Java虚拟机启动之后流逝的时间。 Sample Output 1.729: [GC pause (young) 46M->35M(1332M), 0.0310029 secs]

(2)-XX: PrintGCDateStamps - 为每一项添加日期时间的前缀。 Sample Output 2012-05-02T11:16:32.057 0200: [GC pause (young) 46M->35M(1332M), 0.0317225 secs]

理解G1日志

为了理解这个日志,这部分使用实际的垃圾收集输出日志来明确一些术语。下面的示例列出了输出日志中的术语和值,你会在日志中找到它们。 注意:更多信息请查看Poonam Bajaj’s Blog post on G1 GC logs

G1日志术语索引

Worker Start Parallel Time External Root Scanning Update Remembered Set Scanning Remembered Sets Object Copy Termination Time GC Worker End GC Worker Other Clear CT Other CSet Ref Proc Ref Eng Free CSet 此入门教程源地址上有一些介绍,但是和Poonam Bajaj’s Blog post on G1 GC logs内容几乎相同,所以详细信息请看我翻译的Poonam Bajaj’s Blog post on G1 GC logs

总结

在这个OBE里,你已经对包含在Java虚拟机里的G1垃圾收集器有了大概的认识。首先你学习了为什么堆和垃圾收集器是任何Java虚拟机的关键部件。然后你回顾了使用CMS收集器和G1收集器的垃圾收集是如何工作的。然后你学习了关于G1收集器的命令行开关和使用它们的最佳实践。最后,你学习了如何把对象和数据记录到垃圾收集日志里。

在这个教程里,你已经学到了:

  • Java虚拟机的一些组件
  • G1垃圾收集器概览
  • 回顾CMS收集器
  • 回顾G1收集器
  • 命令行开关和最佳实践
  • G1收集器的日志

0 人点赞