Oracle官方文档笔记《Getting Started with the G1 Garbage Collector》

58 篇文章 0 订阅

Oracle官方文档笔记

《Getting Started with the G1 Garbage Collector》
参考资料

【Oracle官方文档】https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html

https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc.html

https://www.infoq.com/articles/tuning-tips-G1-GC

https://www.oracle.com/technetwork/cn/articles/java/g1gc-1984535-zhs.html#Imp

目的
  • 需学习G1在JVM的运行过程。
  • G1的功能
  • 如何使用G1以及调优

阅读笔记

  • 调优JAVA程序的两个最终目的是:“响应速度Responsiveness”和“吞吐量throughput”

  • 对于“响应速度”以及“吞吐量”的理解可以从三个角度出发“业务角度”、“程序角度”、“底层数据访问”

    • “响应速度”
      业务角度,用户的UI交互响应速度要快
      程序角度,后台处理请求并返回的速度也要快
      数据访问角度,数据库查询的也要快。
    • “吞吐量”
      业务角度,给定时间内事务完成的数量
      程序角度,1个小时内程序作业完成的数量
      数据访问角度,1个小时内数据库查询完成的数量
  • G1定位一个大内存多核心服务器垃圾收集器。它被要求在尽可能快速响应的前提下实现更高的吞吐量。

  • G1与CMS的区别:“无碎片问题”,“可设定期望允许的最大GC暂定时间”
    G1内存结构(图中简化了Humongous区,它弥补了region的无法分配大对象的缺点):
    g1堆.png

  • G1也是采用分代算法来管理内存的分配以及回收,但是它的所有分区都是弹性的不固定大小的

  • G1的垃圾回收过程可以简单抽象为两个过程:“标记“,”评估后疏散回收”。

  • 当标记完成后,对所有region进行评估。把最多垃圾的进行疏散回收,所谓疏散回收就是利用复制算法,把一个或者多个需要回收垃圾的region的存活对象复制到一个空闲的region后,再将它们回收

  • G1能够实现弹性的秘诀在于它可以大概的评估出回收多少个region不容易超出PauseTime.

  • G1不可用精准的计算以及控制响应时间,所以它不是真正意义上的实时性垃圾收集器。

RememberedSets 与 CollectionSets定义

Remembered Sets or RSets track object references into a given region. There is one RSet per region in the heap. The RSet enables the parallel and independent collection of a region. The overall footprint impact of RSets is less than 5%.

Collection Sets or CSets the set of regions that will be collected in a GC. All live data in a CSet is evacuated (copied/moved) during a GC. Sets of regions can be Eden, survivor, and/or old generation. CSets have a less than 1% impact on the size of the JVM

  • RememberedSets 存在每一个region中,用于跟踪该区域对象引用情况(占有不到region5%的空间,是一种空间换取时间的优化手段)。解决了枚举GCRoots时出现跨区引用的问题,类似eden用于跟踪被old区对象引用的CardTable.
  • CollectionSets用于记录region中的存活对象,这些对象在疏散回收的时(复制算法),会被复制到一个新的region(原先所在的region被回收,类似城中村拆迁).
  • G1适用于内存超过6G,PauseTime要求少于0.5s场景。

Note: Unmarked objects == Dead Objects

一步一步学G1

Region size is chosen by the JVM at startup. The JVM generally targets around 2000 regions varying in size from 1 to 32Mb.

  • 一个Region大小可以为1M 2M 4M 8M 16M 32M.整块对内存默认会被划分为2048个region。

  • 如果按照分代算法,G1从逻辑上将离散的region划分为eden类,servivor类,old类,humongous类四种。为什么没有划分servivor1和servivor2呢?因为G1对eden区的回收并非简单采用复制算法,而是采用一种由复制算法改编过来的 “疏散算法”(类似城中村拆迁),这种算法不需要显示交换区域,也不需要指定eden区和servivor区大小比例(真灵活)。

  • humongous类型的region用于存放占用空间超过50%RegionSize的对象,这类型的对象会用一组连续的region来存储,比如每个region为2M,需要分配一个7M的对象,那么就会将它分配到4个连续的humongous类型Region区域,4个region有8M的空间,存放7M的对象会导致最后一个region格子出现1M的内存碎片(4* 2M - 7M = 1M)。
    g1-young区.png

  • Young GC会出现STW(stop the world)

A Young GC in G1

Live objects are evacuated (i.e., copied or moved) to one or more survivor regions. If the aging threshold is met, some of the objects are promoted to old generation regions.

This is a stop the world (STW) pause. Eden size and survivor size is calculated for the next young GC. Accounting information is kept to help calculate the size. Things like the pause time goal are taken into consideration.

  • Eden区和Servivor区的大小时根据上一次YoungGC的统计数据计算的来,此外这个统计数据还可以用于控制PauseTime(由于YoungGC会出现STW,Young区越大,PauseTime越久)

  • 从概念模型上来描述,YoungGC in G1可以理解为(不考虑部分对象晋升到old区):

    (eden)Young区 ,经过YoungGC的疏散算法后
    变成(servivor区 或着 新eden区)Young区
    
  • 并发标记的缺点就是引用关系变化,使得标记作业变得很复杂以及困难。解决办法就是采用SATB算法(全称为Snapshot-At-The-Beginning)为内存产生一张逻辑快照,所有的标记都在快照的访问内发生(相对快照那一刻起内存是静止的)。SATB算法有的缺点就是无法处理浮动垃圾(float garbage)。

  • G1 GC时判断对Region的回收细节上分为2个过程:“通过RSet枚举GCRoot”,“通过GCRoots收集LiveData到CRets”。之后的GC 疏散流程(evacuate)则是面向Region的而非面向堆对象的操作,以region为单位将多region收到一个region中。

  • G1的YoungGC从形式上来理解,相当于是对G1辅助算法,只不过它处理的只是eden区,而核心的G1 GC会覆盖整个堆区,或者说它不会存在分代的概念。收集的概念存在于“YoungGC”和“initialMark阶段,RootRegionScanning阶段”,剩下的“concurentMarking阶段”“Remark阶段”“cleanup筛选回收阶段”,以及最后的“mixedGC混合疏散阶段”,都是不存在分代概念的(正是因为不分代才被称为MixedGC)。

  • YoungGC会在新生代不足的时候触发,而G1的核心GC过程(也叫并发收集过程,或者勉强叫老年代收集也可以),则是在内存使用率达到预设置的阈值时触发(这一点跟CMS算法一致,CMS的默认阈值时80%),默认阈值时45%。

阶段描述
(1) Initial Mark(Stop the World Event)会伴随着一次YoungGC,在YoungGC过程中会标记存在Old引用的servivor Region,这类region,被称为 root region
(2) Root Region Scanning根据root region,并发扫描标记被引用的old区region。
(3) Concurrent Marking根据前两个阶段的扫描结果,对整个堆进行livedata标记(不是标记MarkWork,而是标记Bitmap),这个阶段可以被YoungGC中断。如果发现
(4) Remark(Stop the World Event)重新标记,所有发生改变的引用(包括失活的,以及新加入的引用。)这个阶段利用SATB算法确保不会漏标记,但可能把垃圾也一并标记了,不过不影响可用性。如果发现某些region整个都以及标记为垃圾,那么会直接被free掉(清空并加入freelist,这是一个记录空闲区的列表)。
(5) Cleanup(Stop the World Event and Concurrent)根据标记的统计结果,涮选出垃圾最多的region,在不影响pause time的前提下,对垃圾进行疏散回收。同步RememberedSets日志。最后并发清空empty region后将其添加到free list。
(*) Copying(Stop the World Event)分为YoungGC疏散evacuate,和并发阶段的MixedGC筛选回收疏散evacuate
  • 在重新标记阶段采用了SATB算法会比CMS的重新标记阶段快。(缺点估计是会新增更多的浮动垃圾)

G1调优参数:

G1基本配置:

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

G1经常遇到的两个问题分别是:

  1. 暂停时间
  2. 疏散失败
暂停时间

我们需要评估一个暂停时间,有90%的用户线程允许的最大暂停时间即为MaxGCPauseMillis的最佳暂停时间。这个通常用默认的200ms即可。

疏散失败evacuate failure

导致这个问题原因通常是堆内存不足,堆内存不足的可能原因是:G1 GC启动太晚或者堆空间标记太慢导致GC频率出现太多的浮动垃圾一下子占满了堆空间。万一发生了最坏的情况就是出发SerialOld的full GC.

针对这个问题解决方法:

  1. -XX:G1ReservePercent=n 提高保留用于疏散GC时用的空间
  2. -XX:InitiatingHeapOccupancyPercent=n 设置提前启动G1的时机。
  3. -XX:ConcGCThreads=n 增加并发的线程数。

其他相对不那么重要的参数不在这里解释:
g1-不重要参数.png

遗留问题:
  • SATB算法
  • Remembered Set 与 Card Tables,Concurent refinement thread 的工作原理
高级调优参数:

https://www.oracle.com/technetwork/cn/articles/java/g1gc-1984535-zhs.html#Imp

重要的默认值
G1 GC 是自适应的垃圾回收器,提供了若干默认设置,使其无需修改即可高效地工作。以下是重要选项及其默认值的列表。此列表适用于最新的 Java HotSpot VM build 24。您可以通过在 JVM 命令行输入下列选项和已更改的设置,根据您的应用程序性能需求调整和调优 G1 GC。

-XX:G1HeapRegionSize=n

设置的 G1 区域的大小。值是 2 的幂,范围是 1 MB 到 32 MB 之间。目标是根据最小的 Java 堆大小划分出约 2048 个区域。

-XX:MaxGCPauseMillis=200

为所需的最长暂停时间设置目标值。默认值是 200 毫秒。指定的值不适用于您的堆大小。

-XX:G1NewSizePercent=5

设置要用作年轻代大小最小值的堆百分比。默认值是 Java 堆的 5%。这是一个实验性的标志。有关示例,请参见“如何解锁实验性虚拟机标志”。此设置取代了 -XX:DefaultMinNewGenPercent 设置。Java HotSpot VM build 23 中没有此设置。

-XX:G1MaxNewSizePercent=60

设置要用作年轻代大小最大值的堆大小百分比。默认值是 Java 堆的 60%。这是一个实验性的标志。有关示例,请参见“如何解锁实验性虚拟机标志”。此设置取代了 -XX:DefaultMaxNewGenPercent 设置。Java HotSpot VM build 23 中没有此设置。

-XX:ParallelGCThreads=n

设置 STW 工作线程数的值。将 n 的值设置为逻辑处理器的数量。n 的值与逻辑处理器的数量相同,最多为 8。

如果逻辑处理器不止八个,则将 n 的值设置为逻辑处理器数的 5/8 左右。这适用于大多数情况,除非是较大的 SPARC 系统,其中 n 的值可以是逻辑处理器数的 5/16 左右。

-XX:ConcGCThreads=n

设置并行标记的线程数。将 n 设置为并行垃圾回收线程数 (ParallelGCThreads) 的 1/4 左右。

-XX:InitiatingHeapOccupancyPercent=45

设置触发标记周期的 Java 堆占用率阈值。默认占用率是整个 Java 堆的 45%。

-XX:G1MixedGCLiveThresholdPercent=65

为混合垃圾回收周期中要包括的旧区域设置占用率阈值。默认占用率为 65%。这是一个实验性的标志。有关示例,请参见“如何解锁实验性虚拟机标志”。此设置取代了 -XX:G1OldCSetRegionLiveThresholdPercent 设置。Java HotSpot VM build 23 中没有此设置。

-XX:G1HeapWastePercent=10

设置您愿意浪费的堆百分比。如果可回收百分比小于堆废物百分比,Java HotSpot VM 不会启动混合垃圾回收周期。默认值是 10%。Java HotSpot VM build 23 中没有此设置。

-XX:G1MixedGCCountTarget=8

设置标记周期完成后,对存活数据上限为 G1MixedGCLIveThresholdPercent 的旧区域执行混合垃圾回收的目标次数。默认值是 8 次混合垃圾回收。混合回收的目标是要控制在此目标次数以内。Java HotSpot VM build 23 中没有此设置。

-XX:G1OldCSetRegionThresholdPercent=10

设置混合垃圾回收期间要回收的最大旧区域数。默认值是 Java 堆的 10%。Java HotSpot VM build 23 中没有此设置。

-XX:G1ReservePercent=10

设置作为空闲空间的预留内存百分比,以降低目标空间溢出的风险。默认值是 10%。增加或减少百分比时,请确保对总的 Java 堆调整相同的量。Java HotSpot VM build 23 中没有此设置。

如何解锁实验性虚拟机标志
要更改实验性标志的值,必须先对其解锁。解锁方法是:在命令行中的实验性标志前,显式地设置 -XX:+UnlockExperimentalVMOptions。例如:

java -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=10 -XX:G1MaxNewSizePercent=75 G1test.jar

建议
评估和微调 G1 GC 时,请记住以下建议:

年轻代大小:避免使用 -Xmn 选项或 -XX:NewRatio 等其他相关选项显式设置年轻代大小。固定年轻代的大小会覆盖暂停时间目标。
暂停时间目标:每当对垃圾回收进行评估或调优时,都会涉及到延迟与吞吐量的权衡。G1 GC 是增量垃圾回收器,暂停统一,同时应用程序线程的开销也更多。G1 GC 的吞吐量目标是 90% 的应用程序时间和 10%的垃圾回收时间。如果将其与 Java HotSpot VM 的吞吐量回收器相比较,目标则是 99% 的应用程序时间和 1% 的垃圾回收时间。因此,当您评估 G1 GC 的吞吐量时,暂停时间目标不要太严苛。目标太过严苛表示您愿意承受更多的垃圾回收开销,而这会直接影响到吞吐量。当您评估 G1 GC 的延迟时,请设置所需的(软)实时目标,G1 GC 会尽量满足。副作用是,吞吐量可能会受到影响。
掌握混合垃圾回收:当您调优混合垃圾回收时,请尝试以下选项。有关这些选项的信息,请参见“重要的默认值”:

-XX:InitiatingHeapOccupancyPercent
用于更改标记阈值。
-XX:G1MixedGCLiveThresholdPercent 和 -XX:G1HeapWastePercent
当您想要更改混合垃圾回收决定时。
-XX:G1MixedGCCountTarget 和 -XX:G1OldCSetRegionThresholdPercent
当您想要调整旧区域的 CSet 时。
有关溢出和用尽的日志消息
当您在日志中看到目标空间溢出/用尽的消息时,意味着 G1 GC 没有足够的内存,供存活者和/或晋升对象使用。Java 堆不能扩展,因为已达到最大值。示例消息:

924.897: [GC pause (G1 Evacuation Pause) (mixed) (to-space exhausted), 0.1957310 secs]

924.897:[GC pause (G1 Evacuation Pause) (mixed) (to-space overflow), 0.1957310 secs]

要缓解此问题,请尝试以下调整:

增加 -XX:G1ReservePercent 选项的值(并相应增加总的堆大小),为“目标空间”增加预留内存量。

通过减少 -XX:InitiatingHeapOccupancyPercent 提前启动标记周期。

您也可以通过增加 -XX:ConcGCThreads 选项的值来增加并行标记线程的数目。

有关这些选项的描述,请参见“重要的默认值”。

巨型对象和巨型分配
对于 G1 GC,任何超过区域一半大小的对象都被视为“巨型对象”。此类对象直接被分配到老年代中的“巨型区域”。这些巨型区域是一个连续的区域集。StartsHumongous 标记该连续集的开始,ContinuesHumongous 标记它的延续。

在分配任何巨型区域之前,会检查标记阈值,如有必要,还会启动一个并发周期。

在清理阶段或完整的垃圾回收周期内,标记周期结束时会清理死亡的巨型对象。

为了减少复制开销,巨型对象未包括在疏散暂停中。完整的垃圾回收周期会对巨型对象进行压缩。

由于每个 StartsHumongous 和 ContinuesHumongous 区域集只包含一个巨型对象,所以没有使用巨型对象的终点与上个区域的终点之间的空间(即巨型对象所跨的空间)。如果对象只是略大于堆区域大小的倍数,则此类未使用的空间可能会导致堆碎片化。

如果巨型分配导致连续的并发周期,并且此类分配导致老年代碎片化,请增加 -XX:G1HeapRegionSize,这样一来,之前的巨型对象就不再是巨型对象了,而是采用常规的分配路径。

总结
G1 GC 是区域化、并行-并发、增量式垃圾回收器,相比其他 HotSpot 垃圾回收器,可提供更多可预测的暂停。增量的特性使 G1 GC 适用于更大的堆,在最坏的情况下仍能提供不错的响应。G1 GC 的自适应特性使 JVM 命令行只需要软实时暂停时间目标的最大值以及 Java 堆大小的最大值和最小值,即可开始工作。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值