G1垃圾收集器

[译]G1垃圾收集器

原文地址:Getting Started with the G1 Garbage Collector
原文作者:https://www.oracle.com
译文出自:https://github.com/cap-ljf/blog
本文永久链接:https://github.com/cap-ljf/blog/blob/master/tech/java/jvm/G1垃圾收集器.md
译者:cap-ljf

概览

目的

这篇教程介绍了如何使用G1垃圾收集器的基础在HotSpot JVM中如何使用G1。你可以学习到G1收集器的内部工作原理、切换使用G1收集器的关键命令和记录其日志的操作。

阅读耗时

大约1个小时

介绍

这篇文章涵盖了Java中JVM虚拟机G1垃圾收集器的基础知识。在这篇文章的第一部分,对JVM垃圾收集器和性能有一个概览,接着同学们又回顾了CMS垃圾收集器的工作原理,一步一步的介绍了G1垃圾收集器在HotSpot JVM中的使用方法。接下来,一个小节介绍了G1垃圾收集器的JVM参数。最后,你会学习到G1收集器的JVM日志。

硬件和软件要求

下面是硬件和软件要求列表:

  • A PC running Windows XP or later, Mac OS X or Linux. Note that the hands on is done with Windows 7 and has not been tested on all platforms. However, everything should work fine on OS X or Linux. Also a machine with more than one core is preferable.
  • Java 7 Update 9 or later
  • The latest Java 7 Demos and Samples Zip file

前提条件

在这篇教程开始前,你应该:

  • If you have not done so, download and install the latest version of the Java JDK (JDK 7 u9 or later).
    Java 7 JDK Downloads。
  • Download and install the Demos and Samples zip file from the same location. Unzip the file and place the contents in a directory. For example: C:\javademos

G1垃圾收集器

G1垃圾收集器是一个服务端的垃圾收集器,目标是用于有大内存的多处理器机器。它在尽可能的达到预期GC暂停时间的同时,达到了高吞吐率。G1垃圾收集器在JDK 7 update 4 和更后的更新中完全的支持。G1垃圾收集器专为以下应用而设计:

  • 可以像CMS收集器一样和用户线程并发执行
  • 整理空闲空间时不需要长时间的GC停顿
  • 需要更可靠的GC停顿时间
  • 不需要牺牲太多吞吐量
  • 不需要使用大的Java堆内存

G1被计划用于CMS收集器的长期替代品。对比CMS和G1,有很多不同之处使G1成为更好的解决方案。一点不同是G1会整理内存。G1能整理以完全的避免使用细粒度空闲列表来进行内存分配,依赖于区块。这相当大的简化了垃圾收集器的各个部分,同时最大程度的避免了潜在的碎片问题。并且,比CMS而言G1提供了更多可预测的垃圾收集停顿时间,允许用户设置期望的停顿时间。

G1 操作概览

旧时代的垃圾收集器(Serial、parallel、CMS)都将堆划分为三部分固定大小的空间:年轻代、老年代、永久代。
Alt text

所有的内存对象都分配在这三块内存里。
G1收集器采用了一种不同的方法。
Alt text

堆被分成相同大小的堆区域(region),每一个region都是虚拟内存的连续范围。某些region集合分配了与旧收集器中相同的角色(eden、survivor、old),但他们没有一个固定的大小。这为内存提供了更大的灵活性。

当执行垃圾回收时,G1以类似于CMS收集器的方式运行。G1执行一个全局并发标记阶段去决定整个堆中存活的对象。在标记阶段完成后,G1知道那些regions是最空的。它优先收集会产生更多空闲空间的regions。这就是为什么这种垃圾收集方法被称为Garbage-First。G1将其收集和压缩活动集中在可能充满可回收对象的区域(即垃圾)。G1使用停顿预测模型来满足用户设定的目标停顿时间,并根据具体的停顿时间选择region的收集数量。

由G1标记为成熟的可收集的regions 是使用疏散的方式进行垃圾收集。G1从一个或多个region拷贝对象至一个region,在这个过程中同时完成了清除空间和整合内存。这个动作在多处理器上并行执行,以减少暂停时间和增大吞吐量。因此,对于每次垃圾收集,G1会持续工作以减少碎片,在用户定义的暂停时间内工作。这超出了之前的两种方法的能力:CMS收集器不进行压缩,Parallel Old垃圾收集器仅执行整个堆的压缩,这会导致相当长的停顿时间。

要注意:G1不是实时收集器。它尽可能的满足目标停顿时间但不能保证绝对满足。基于先前集合的数据,G1确定在用户指定的目标时间内可以收集的region数量。因此,G1收集器有一个相当准确的模型计算收集区域的耗时,并使用这个模型来决定收集哪些regions来保证停顿时间在目标范围内。

注意: G1有并发(和应用线程同时执行,例如:refinement, marking, cleanup)和并行(多线程执行,例如:STW)阶段。Full GC仍然是单线程的,但如果正确调整,您的应用程序应该避免使用Full GC。

G1足迹

如果你从Parallel Old GC或CMS收集器转到G1,你会看到JVM进程占用了更多的内存空间。这主要和“统计”数据结构有关,例如Remembered SetsCollection Sets

  • Remembered Sets or RSets 跟踪所有指向固定region的对象引用。在堆中每个region都有一个RSet。RSet支持并行和独立收集region。RSets的总体足迹影响小于5%。
  • Collection Sets or CSets是在某次GC过程中将被收集的region集合。在GC期间,CSet中的所有实时数据都被撤离(复制/移动)。这些regions集合可以是eden、survivor、或者old generation。CSets对JVM的影响小于1%。

推荐的G1用例

G1是专注于给要求有限的GC延迟(GC停顿)的大内存应用程序提供一种解决方案。这意味着堆内存应至少在6GB以上,并且GC停顿时间应可预测并稳定在0.5秒以内。

如果应用程序具有以下一个或多个特征,那么你现在使用的Parallel Old和CMS都应该切换到G1:

  • Full GC持续时间长而且频繁
  • 对象分配率和晋升变化显著
  • 不希望有GC停顿时间或压缩时间太长(大于0.5-1秒)

注意: 如果您正在使用Parallel Old或CMS,并且应用程序没有经历长时间的GC停顿,那就没必要切换。更改为G1收集器不是使用最新JDK的必要条件。

详解G1垃圾收集器

G1垃圾收集器采用了一种不同的方法来分配内存。下面的图片回顾了G1收集器。

1. G1堆结构

堆大小被分为多个固定大小的region。
Alt text

region的大小可以在JVM启动时设置,JVM内存目标通常分为2000个region,每个region大小为1~32Mb。

2. G1堆分配

实际上,这些regions被映射为Eden、Survivor和Old的逻辑空间。
Alt text

上图使用不同的颜色标识已分配的不同角色的region。活动对象从一个region移动到另一个region。region被设计为收集时可以不停止其他用户线程。

正如上图展示的——region可以被分配为Eden、Survivor和Old region。另外,还有第四种对象类型——Humongous(巨型对象) region。这些regions被设计用来储存这些超大对象,这些对象一般大于region大小的50%。它们存储为一组连续的区域,最后一种类型的区域将是堆的未使用区域。

注意: 在此篇文章撰写时,H对象的收集方式还未优化。因此,你应该避免创建这种超大对象。

3. G1年轻代

堆被分成大约2000个region。region最小1Mb,最大32Mb。下图蓝色为老年代region,绿色为年轻带region。
Alt text

请注意,region不需要像旧垃圾收集器那样连续。

4. Young GC in G1

存活的年轻代对象会被移动到一个或多个幸存者区域。如果对象满足老化阈值,则将一些对象提升到老年代region。
Alt text

这个过程会造成STW暂停。Eden和Survivor大小在下一次YOUNG GC时重新计算,之前保存的统计信息会用来辅助计算这个大小,统计信息:例如GC目标暂停时间。

这种方法使改变region大小更容易进行。

5. G1 YOUNG GC 结束后

存活的年轻代对象都被移动到survivor region或晋升到old region。
Alt text

近期晋升的对象被标记为深蓝色,survivor region是绿色。

总结,G1垃圾收集器的young GC:

  • 堆内存被分为多个region;
  • 年轻代由多个不连续的reign集组成,这让它更容易改变大小;
  • Young GC会造成STW;
  • Young GC使用多线程并行执行;
  • 活着的对象会被移动到新的survivor region或者晋升代Old region;

G1老年代收集
像CMS收集器一样,G1收集器可以低停顿时间收集老年代对象。下面的表格描述了G1的老年代收集阶段。
G1收集器阶段——并发标记循环阶段
老年代收集有以下几个阶段,注意有部分阶段是年轻代阶段的一部分。

阶段描述
(1)初始标记(STW)这是个STW过程。它可能在YGC时发生,标记引用Old region的Survivor(Root) region。
(2)Root region扫描扫描引用old region的survivor regions。这个阶段与应用县线程并发执行。注意:此阶段必须在YGC发生前完成
(3)并发标记找到整个堆中存活的对象,这个阶段与应用线程并发执行,这个阶段可以被YGC中断
(4)重新标记(STW)完成堆中存活对象的标记,这个过程使用一种叫做SATB的算法(此算法比CMS使用的算法更加快速的标记)
(5)清理(STW、并发)对存活对象和free区域进行统计(STW);擦除Remember Set(STW);重置空白区域并放入free list(并发)。
(*)复制(STW事件)将存活对象移动到新的未使用的region会造成STW;这个动作在年轻代中记录为[GC pause(young)],或年轻代和老年代都有的[GC pause(mixed)]

详解G1老年代收集

通过上面的阶段定义,让我们看看G1收集器的老年代垃圾收集是如何工作的。

1. 初始标记阶段

初始标记存活对象过程发生在YGC上。在日志中打印为:GC pause (young)(inital-mark)
Alt text

2. 并发标记阶段

如果找到空region(下图中的’X’),则在remark阶段立即将它们移除。此外,确定存活度的统计信息会被计算。
Alt text

3. remark阶段

空区域被移除并回收,现在计算所有region的活跃度。
Alt text

4. 复制/清除阶段

G1选择那些具有最低“活跃度”的region,那些可以最快收集的reigon。这些region的收集与YGC同时发生。这在日志中表示为:[GC pause (mixed)],所以YGC和Old GC在同一时间收集。
Alt text

10. 清理之后

选择的区域已经被收集并压缩成图中所示的深蓝色区域和深绿色区域。
Alt text

Old GC总结

  • 并发阶段:
    • 活跃度计算和应用程序并行
    • 这些region的活跃度信息会被计算出 规定时间内最佳回收region。
    • 没有和CMS一样的并发清理阶段
  • 重标记阶段
    • 使用SATB算法比CMS更优
    • 完成空region的回收
  • 复制/清理阶段
    • YGC和Old GC同时执行
    • 根据活跃度选择要收集的老年代region

JVM命令最佳实践

这个章节我们来学习一下G1收集器的JVM参数。

基本命令

开启G1垃圾收集器:-XX:+UseG1GC
下面的简单命令是用来开启JDK中的示例代码Java2Demo

java -Xmx50m -Xms50m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar c:\javademos\demo\jfc\Java2D\Java2demo.jar
关键命令开关

-XX:+UseG1GC:JVM开启G1垃圾收集器
-XX:MaxGCPauseMillis=200:设置一个最大的GC停顿目标时间。这是一个软目标,JVM会尽最大的努力达到这个目标。因此,这个目标有时候达不到。默认值为200ms。
-XX:InitiatingHeapOccupancyPercent=45:整个堆使用率达到某个值时开启一个并发GC周期。默认值时45%。

最佳实践

使用G1时有一些非常有用的技巧。
不要设置年轻代的大小
通过JVM参数-Xmn显示设置年轻代大小与G1垃圾收集器的默认行为。

  • G1将不会尊重GC停顿时间。所以本质上,设置年轻代大小会使GC停顿时间目标失效。
  • G1将不能扩展和缩小年轻代大小空间。所以这个大小是固定的,不能改变。

响应时间指标
考虑设置90%的请求达到这个停顿目标来代替使用XX:MaxGCPauseMillis=<N>作为平均响应时间作为一个指标。这意味着90%的请求响应时间都不会大于停顿目标时间。记住,这个目标停顿时间并不能保证总是达到。
什么是Evacuation Failure?
它指由于JVM用尽了堆空间regions导致survivor和晋升的对象晋升失败。此时堆不能再扩展因为它已经是最大了。使用XX:+PrintGCDetails打印出to-space overflow情况的GC日志。这个代价是非常昂贵的!

  • GC会继续,所以空间必须要清理出来
  • 未成功复制移动的对象会被持久化
  • CSet中的regions的RSets的任何更新都要重新生成
  • 上面的操作代价都很高

怎么比喵 Evacuation Failure
为了避免 Evacuation Failure,考虑下面的操作:

  • 增加堆大小
    • 增加 -XX:G1ReservePercent=n, the default is 10.
    • G1通过释放保留空间来创建一个假天花板,以防需要更多的天花板
  • 更早的开启标记循环
  • 使用-XX:ConcGCThreads=n命令增加标记线程数量
完整的G1 GC参数

这是G1 GC的完整参数,记得使用上面的最佳实践方法:
Alt text

G1 GC日志

最后一个主题我们需要知道通过G1 GC日志来分析G1收集器的表现。这个章节提供了一个命令速览(收集数据及打印出来的日志信息)。

设置日志详情

你可以设置三种不同的日志级别。
(1)-verbosegc (等同于-XX:+PrintGC)设置日志详情级别为fine
示例输出

[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扫描,对象拷贝,最终操作
  • 也展示其他时间例如花费在选择CSet的时间,参考过程,引用处理和释放CSet
  • 展示Eden、Survivor和整个堆的使用情况

示例输出

[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类似但还包含了每个单独工作线程的信息

[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]
确定时间

多个命令组合来控制GC日志中的时间展示。
(1) -XX:+PrintGCTimeStamps 展示从JVM启动到现在过去的时间
示例

1.729: [GC pause (young) 46M->35M(1332M), 0.0310029 secs]

(2) -XX:+PrintGCDateStamps 在每条日志前加上每天的时间前缀

2012-05-02T11:16:32.057+0200: [GC pause (young) 46M->35M(1332M), 0.0317225 secs]
理解G1日志

要了解G1日志,本节使用实际日志输出定义了许多术语。
**提示:**更多的信息请查阅 Poonam Bajaj’s Blog post on G1 GC logs.

G1日志术语索引

Parallel Time
414.557: [GC pause (young), 0.03039600 secs] [Parallel Time: 22.9 ms]
[GC Worker Start (ms): 7096.0 7096.0 7096.1 7096.1 706.1 7096.1 7096.1 7096.1 7096.2 7096.2 7096.2 7096.2
Avg: 7096.1, Min: 7096.0, Max: 7096.2, Diff: 0.2]
Parallel Time – Overall elapsed time of the main parallel part of the pause
Worker Start – Timestamp at which the workers start
Note: The logs are ordered on thread id and are consistent on each entry

External Root Scanning

[Ext Root Scanning (ms): 3.1 3.4 3.4 3.0 4.2 2.0 3.6 3.2 3.4 7.7 3.7 4.4
Avg: 3.8, Min: 2.0, Max: 7.7, Diff: 5.7]
External root scanning - The time taken to scan the external root (e.g., things like system dictionary that point into the heap.)

Update Remembered Set

[Update RS (ms): 0.1 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 Avg: 0.0, Min: 0.0, Max: 0.1, Diff: 0.1]
[Processed Buffers : 26 0 0 0 0 0 0 0 0 0 0 0
Sum: 26, Avg: 2, Min: 0, Max: 26, Diff: 26]
Update Remembered Set - Any buffers that are completed but have not yet been processed by the concurrent refinement thread before the start of the pause have to be updated. Time depends on density of the cards. The more cards, the longer it will take.

Scanning Remembered Sets

[Scan RS (ms): 0.4 0.2 0.1 0.3 0.0 0.0 0.1 0.2 0.0 0.1 0.0 0.0 Avg: 0.1, Min: 0.0, Max: 0.4, Diff: 0.3]F
Scanning Remembered Sets - Look for pointers that point into the Collection Set.

Object Copy

[Object Copy (ms): 16.7 16.7 16.7 16.9 16.0 18.1 16.5 16.8 16.7 12.3 16.4 15.7 Avg: 16.3, Min: 12.3, Max: 18.1, Diff: 5.8]
Object copy – The time that each individual thread spent copying and evacuating objects.

Termination Time

[Termination (ms): 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 Avg: 0.0, Min: 0.0, Max: 0.0, Diff: 0.0] [Termination Attempts : 1 1 1 1 1 1 1 1 1 1 1 1 Sum: 12, Avg: 1, Min: 1, Max: 1, Diff: 0]
Termination time - When a worker thread is finished with its particular set of objects to copy and scan, it enters the termination protocol. It looks for work to steal and once it’s done with that work it again enters the termination protocol. Termination attempt counts all the attempts to steal work.

GC Worker End

[GC Worker End (ms): 7116.4 7116.3 7116.4 7116.3 7116.4 7116.3 7116.4 7116.4 7116.4 7116.4 7116.3 7116.3
Avg: 7116.4, Min: 7116.3, Max: 7116.4, Diff: 0.1]
[GC Worker (ms): 20.4 20.3 20.3 20.2 20.3 20.2 20.2 20.2 20.3 20.2 20.1 20.1
Avg: 20.2, Min: 20.1, Max: 20.4, Diff: 0.3]
GC worker end time – Timestamp when the individual GC worker stops.

GC worker time – Time taken by individual GC worker thread.

GC Worker Other

[GC Worker Other (ms): 2.6 2.6 2.7 2.7 2.7 2.7 2.7 2.8 2.8 2.8 2.8 2.8
Avg: 2.7, Min: 2.6, Max: 2.8, Diff: 0.2]
GC worker other – The time (for each GC thread) that can’t be attributed to the worker phases listed previously. Should be quite low. In the past, we have seen excessively high values and they have been attributed to bottlenecks in other parts of the JVM (e.g., increases in the Code Cache occupancy with Tiered).

Clear CT

[Clear CT: 0.6 ms]
Time taken to clear the card table of RSet scanning meta-data

Other

[Other: 6.8 ms]
Time taken for various other sequential phases of the GC pause.

CSet

[Choose CSet: 0.1 ms]
Time taken finalizing the set of regions to collect. Usually very small; slightly longer when having to select old.

Ref Proc

[Ref Proc: 4.4 ms]
Time spent processing soft, weak, etc. references deferred from the prior phases of the GC.

Ref Enq

[Ref Enq: 0.1 ms]
Time spent placing soft, weak, etc. references on to the pending list.

Free CSet

[Free CSet: 2.0 ms]
Time spent freeing the set of regions that have just been collected, including their remembered sets.

总结

通过这篇文章,你已经对G1垃圾收集器有了整体的认识。首先你学习了堆和垃圾收集器如何作为JVM的关键部分。接着你复习了CMS和G1的垃圾收集器是如何工作的。接着你学习了G1收集器的JVM命令和它们的最佳实践。最后,你学习了如果打印日志对象和数据。

在这篇教程中,你学习了:

  • Java JVM的组成部分
  • G1收集器的总览
  • 复习CMS收集器
  • 复习G1收集器
  • G1 JVM参数和最佳实践
  • G1日志
资源

更多的G1信息和资源参考以下链接:
Java HotSpot VM Options
The Garbage First(G1) Garbage Collector
Poonam Bajaj G1 GC Blog Post
Java SE 7: Develop Rich Client Applications
Java Performance - Charlie Hunt and Binu John
Oracle Learning Library

鸣谢
  • Curriculum Developer: Michael J Williams
  • QA: Krishnanjani Chitta
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值