本文详细介绍G1垃圾收集器的参数配置,如何进行性能调优, 以及怎样对GC性能进行分析和评估。

文章目录

  • 0. G1简介
  • 1. 垃圾回收阶段简介
  • 2. 纯年轻代模式的垃圾收集
  • 3. 混合模式的垃圾收集
  • 4. 标记周期的各个阶段
  • 5. 常用参数与默认值
    • `-XX:G1HeapRegionSize=n`
    • `-XX:MaxGCPauseMillis=200`
    • `-XX:G1NewSizePercent=5`
    • `-XX:G1MaxNewSizePercent=60`
    • `-XX:ParallelGCThreads=n`
    • `-XX:ConcGCThreads=n`
    • `-XX:InitiatingHeapOccupancyPercent=45`
    • `-XX:G1MixedGCLiveThresholdPercent=65`
    • `-XX:G1HeapWastePercent=10`
    • `-XX:G1MixedGCCountTarget=8`
    • `-XX:G1OldCSetRegionThresholdPercent=10`
    • `-XX:G1ReservePercent=10`
  • 6. 如何解锁实验性质的JVM参数
  • 7. 最佳实践与建议
  • 8. GC日志中内存溢出和内存耗尽的信息
  • 9. 大对象/巨型对象的内存分配
  • 10. 总结
  • 作者简介
  • 相关资源和链接

0. G1简介

G1的全称为 Garbage First Garbage Collector, 是一款内置在HotSpot JVM 中的服务端垃圾收集器。
G1使用【分代算法】, 将GC过程拆解为多个并发和并行阶段,将暂停时间打散,从而实现了低延迟特性,并保持良好的吞吐量。
只要G1认为可以进行垃圾收集,就会触发一次GC, 当然,G1优先回收存活数据较少的区域。
存活数据少就表示里面的垃圾对象多,这也是名字 Garbage First 的由来。

垃圾收集器本质上是一款内存管理工具。 G1算法主要通过以下方式来实现自动内存管理:

  • 【分代】在年轻代中分配新对象,达到一定年龄的对象则提升到老年代。
  • 【并发】在并发标记阶段遍历老年代中的所有存活对象。 只要Java中堆内存的总使用量超过阈值,HotSpot 就会触发标记周期。
  • 【整理】通过并行复制方式来整理存活对象,释放可用内存。

在GC中, 并行(parallel)是指多个GC线程一起干活, 并发(concurrent)指GC线程和业务线程一起并发执行。

本文先简要介绍怎样配置G1参数, 然后再介绍如何对GC性能进行分析和评估。
想要进行GC调优,至少要对 Java的垃圾收集机制 有一定了解。

G1是一款增量式的分代垃圾收集器。 什么是增量呢?
G1把堆内存分为很多个大小相同的【小区域、小块】(region)。
在JVM启动时,根据堆内存的配置,确定每个region的大小。 region的大小取值范围是 1MB32MB,总数一般不会超过2048

在G1中,新生代(eden),存活区(survivor)和老年代(old generation)都是逻辑上的概念,由这些region组合而成,这些region之间并不需要保持连续。

可以设置参数来指定 “期望的最大暂停时间”, G1会尽量去满足这个软实时目标值。
在【纯年轻模式(young)】的垃圾收集过程中,G1可以动态调整年轻代的大小(eden + survivor),以达成这个软实时目标暂停时间。
在【混合模式(mixed)】的垃圾收集过程中,G1可以调整本次GC需要回收的老年代region数量,取决于【要回收的总region数】,【每个region中存活对象的百分比】,以及【堆内存允许浪费的比例】等数据。

G1采用【增量并行复制】的方式来实现【堆内存碎片整理功能】,将回收集之中的存活对象拷贝到新region中,回收集的英文是 Collection Set,简称CSet,也就是本次GC涉及的region集合。
目标是尽可能多地,从有空闲的region中回收堆内存,同时也试图达成预期的暂停时间指标。

G1为每个region都单独设置了一份【记忆集】,英文是 Remembered Set,简称 RSet, 用来跟踪记录从别的region指向这个region中的引用。
通过这种region划分和独立的RSet数据结构,G1就可以并行地进行增量式垃圾回收,而不用遍历整个堆内存。
因为只需要扫描RSet,就可以得知有哪些跨区的引用指向这个region,从而对这些region进行回收。
G1使用【后置写屏障】(post-write barrier)来记录堆内存的修改信息, 并负责更新RSet。

1. 垃圾回收阶段简介

G1垃圾收集器的纯年轻代模式GC,以及混合模式GC, 除了转移暂停(evacuation pause)这个 STW 阶段之外,还有并行的、并发的,由多个子阶段组成的标记周期。
G1 使用开始快照算法(SATB,Snapshot-At-The-Beginning),在标记周期开始时,对堆内存中的存活对象信息进行一次快照。
那么,总的存活对象就包括开始快照中的存活对象,加上标记开始之后新创建的对象。
G1的标记算法使用【前置写屏障】(pre-write barrier)来记录和标记逻辑上属于这次快照的对象。

2. 纯年轻代模式的垃圾收集

G1将绝大部分的内存分配请求打到eden区。
在年轻代模式的垃圾收集过程中,G1会收集eden区和前一次GC使用的存活区。
并将存活对象拷贝/转移到一些新的region里面, 具体拷贝到哪里则取决于对象的年龄;
如果达到一定的GC年龄,就会转移/提升到老年代中;否则就会转移到存活区。
本次的存活区则会被加入到下一次年轻代GC/混合模式GC的CSet中。

3. 混合模式的垃圾收集

并发标记周期执行完毕之后,G1则会从纯年轻模式切换到混合模式。
在执行混合模式的垃圾收集时,G1会选择一部分老年代region加入回收集,当然,每次的回收集都包括所有eden区和存活区。
具体一次添加多少个老年代region,由哪些参数来决定,将会在后面进行讨论。
经过多次混合模式的垃圾收集之后,很多老年代region其实已经处理过了,然后G1又切换回纯年轻代模式,直到下一次的并发标记周期完成。

4. 标记周期的各个阶段

G1的标记周期包括以下这些阶段:

  • 【初始标记阶段】(Initial mark phase): 在此阶段标记 GC roots, 一般是附加在某次常规的年轻代GC中顺带着执行。
  • 【扫描GC根所在的region】(Root region scanning phase): 根据初始标记阶段确定的GC根元素,扫描这些元素所在region,获取对老年代的引用,并标记被引用的对象。 该阶段与应用线程并发执行,也就是说没有STW停顿,必须在下一次年轻代GC开始之前完成。
  • 【并发标记阶段】(Concurrent marking phase)”: 遍历整个堆,查找所有可达的存活对象。 此阶段与应用线程并发执行, 也允许被年轻代GC打断。
  • 【再次标记阶段】(Remark phase): 此阶段有一次STW暂停,以完成标记周期。 G1会清空SATB缓冲区,跟踪未访问到的存活对象,并进行引用处理。
  • 【清理阶段】(Cleanup phase): 这是最后的子阶段,G1在执行统计和清理RSet时会有一次STW停顿。 在统计过程中,会把完全空闲的region标记出来,也会标记出适合于进行混合模式GC的候选region。 清理阶段有一部分是并发执行的,比如在重置空闲region并将其加入空闲列表时。

5. 常用参数与默认值

G1是一款自适应垃圾收集器,大部分的参数都有默认值,一般情况下无需太多配置即可高效运行。
下面列出常用参数和对应的默认值, 如果有特殊需求,可调整JVM启动参数,以满足特定的性能指标。

-XX:G1HeapRegionSize=n

用来设置G1 region 的大小。 必须是2的幂(x次方),允许的范围是 1MB32MB
这个参数的默认值, 会根据堆内存的初始大小(-Xms)与最大值(-Xmx)动态调整,以便将堆内存切分为2048个左右的region。

-XX:MaxGCPauseMillis=200

期望的最大暂停时间。 默认值为200毫秒。 这个值不会自动调整,启动时设置为多少就是多少。

-XX:G1NewSizePercent=5

设置年轻代的最小空间占比, 默认值为5,相当于最少有5%的堆内存会作为年轻代来使用。
这个参数会覆盖 -XX:DefaultMinNewGenPercent
这是实验性质的参数,后续版本有可能会有变更。

-XX:G1MaxNewSizePercent=60

设置年轻代的最大空间占比。 默认值为60,相当于最多有60%的堆内存会作为年轻代来使用。
此设置会覆盖 -XX:DefaultMaxNewGenPercent
这是实验性质的参数,后续版本有可能会有变更。

-XX:ParallelGCThreads=n

设置STW阶段的并行worker线程数。

  • 如果逻辑处理器小于等于8个,则默认 n 等于逻辑处理器的数量。
  • 如果逻辑处理器大于8个,则 n 默认约等于处理器数量的5/8 + 3
  • 如果是高配置的 SPARC 系统,则默认 n 大约等于逻辑处理器数量的5/16
  • 大多数情况下使用默认值即可。
  • 有一种情况除外,就是Docker容器中使用了低版本JDK,案例参考: JVM 问题排查分析下篇(案例实战)。

-XX:ConcGCThreads=n

设置并发标记的GC线程数。 默认值约等于 ParallelGCThreads 值的 1/4

-XX:InitiatingHeapOccupancyPercent=45

设置标记周期的触发阈值, 即Java堆内存使用率的百分比。 默认的触发阈值是整个Java堆的45%

-XX:G1MixedGCLiveThresholdPercent=65

执行混合模式GC时,根据老年代region的使用率,确定是否包含到回收集之中。 阈值默认为65%
此设置会覆盖 -XX:G1OldCSetRegionLiveThresholdPercent
这是实验性质的参数,后续版本有可能会有变更。

-XX:G1HeapWastePercent=10

设置可以容忍的堆内存浪费率百分比。
如果可回收的堆内存占比小于这个阈值比例,则 HotSpot 不会启动混合模式GC。
默认值为10%

-XX:G1MixedGCCountTarget=8

在标记周期完成后,期望执行多少次混合模式的GC,直到存活数据的比例降到 G1MixedGCLiveThresholdPercent 之下。
默认是执行8次混合模式的GC。 具体执行的次数一般都会小于这个值。

-XX:G1OldCSetRegionThresholdPercent=10

混合模式的GC中,每次处理的老年代 region 数量上限占比。 默认值为Java堆的10%

-XX:G1ReservePercent=10

设置一定比例的保留空间, 让其保持空闲状态,降低 to空间 内存不足的风险。 默认值为 10%
虽然这是一个百分比,但实际会映射为具体的大小,所以当增加或减少百分比时,最好将Java堆的总大小也进行同样大小的调整。

6. 如何解锁实验性质的JVM参数

要修改实验性质的JVM参数值,必须先进行声明。
我们可以在命令行参数中,设置实验性质的参数之前,明确指定 -XX:+UnlockExperimentalVMOptions。 例如:

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

7. 最佳实践与建议

调整G1参数之前,需要记住以下几点:

  • 禁止设置年轻代的大小: 不要使用 -Xmn-XX:NewRatio 之类的选项来指定年轻代的大小。 如果指定固定的年轻代大小,则会覆盖最大暂停时间目标,可以说得不偿失。
  • 期望的最大暂停时间值: 不管对哪一款垃圾收集器进行调优,都需要在延迟与吞吐量指标之间进行权衡。
    G1是一款具有统一暂停时间的增量式垃圾收集器, 所以对CPU资源的开销相对要大一些。 G1的吞吐量目标,是指在 高负载 场景下,确保应用线程占有90%以上的CPU时间,GC线程的开销保持在10%以下。
    相比之下,HotSpot中自带的高吞吐量垃圾收集器可以优化到 99% 的应用线程时间, 也就是说只有不到1%的GC开销。
    因此,在压测G1的吞吐量指标时,需要放宽暂停时间指标。 如果设定的暂停时间目标值太小,就表示你愿意承担较大的GC开销,但这会影响到吞吐量。 在压测 G1 的延迟指标时,可以设置期望的软实时暂停时间指标,G1会尽力达成此目标。 副作用则是吞吐量会受到影响。
  • 对大部分服务端应用程序来说,CPU负载不会超过50%,即使GC多占了一点CPU也影响不大,因为还有很多冗余, 我们更关注的是GC暂停时间,因为这关系到响应延迟指标。
  • 混合模式的GC: 在调优混合模式的GC时,可以尝试以下选项。 这些选项的详细信息请参考前面的小节:
    • -XX:InitiatingHeapOccupancyPercent: 设置标记周期的触发阈值。
    • -XX:G1MixedGCLiveThresholdPercent-XX:G1HeapWastePercent: 调整混合模式GC相关的策略。
    • -XX:G1MixedGCCountTarget-XX:G1OldCSetRegionThresholdPercent 用于优化调整CSet中的老年代region比例。

8. GC日志中内存溢出和内存耗尽的信息

如果我们在GC日志中看到 to-space overflow/exhausted, 则表明G1没有足够的内存来存放存活区或者需要提升的对象,或者两者都不足。 这时候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 选项的值, 以增加保留的 “to-space” 大小,一般来说,堆内存的总大小也需要相应地加大。
  • 降低 -XX:InitiatingHeapOccupancyPercent 来尽早触发标记周期。
  • 适当加大 -XX:ConcGCThreads 选项的值,增加并发标记的线程数。

这些选项的具体信息,请参考前面的描述。

9. 大对象/巨型对象的内存分配

如果某个对象超过单个 region 空间的一半,则会被G1视为 【大对象/巨型对象】(Humongous object)。 例如一个很大的数组或者String
这样的对象会直接分配到老年代的 “大对象region区(Humongous region)”。 一个大对象region区就是一组虚拟地址空间连续的region。 StartsHumongous 标志着开头的region,而 ContinuesHumongous 则标记随后的region集合。

在分配大对象region区之前,G1会先判断是否达到开启标记周期的阈值,在必要时会启动并发标记周期。

在标记周期最后的清理阶段,以及FullGC的清理过程中,都会释放不再使用的巨型对象。

为了减少内存复制的开销,所有转移暂停GC都不进行巨型对象的压缩和整理。 Full GC 时才会将巨型对象整理到位。

由于每个 StartsHumongous 和 ContinuesHumongous 组成的集合中都只保存一个巨型对象, 因此这个组合内部,最后面的空间总有一部分是浪费的。
如果某个对象占用的空间,只比N个region大上那么一点点,那么未使用的那部分空间实际上就产生了内存碎片。

如果在GC日志中,看到由 Humongous 分配而触发的大量并发周期,而且在老年代中形成了大量的内存碎片,就需要加大 -XX:G1HeapRegionSize 的值,让之前的巨型对象不再被当成巨无霸,而是走常规的对象分配方式【只要其小于region的50%即可】。

10. 总结

G1是一款 【并行+并发】 方式的【增量】垃圾收集器,将堆内存划分为很多个region,与其他 GC 算法实现相比,提供了可预测性更精准的暂停时间。
增量特性使得G1可以处理更大的堆内存空间,在最坏情况下依然保持合理的响应时间。

G1具有自适应特性,一般情况下,只需要设置3个调优参数即可:

  • 期望的最大暂停时间, 例如 -XX:MaxGCPauseMillis=50
  • 堆内存的最大值, 例如 -Xmx4g
  • 堆内存的最小值, 例如 -Xms4g

作者简介

Monica Beckwith, Oracle技术工作组的核心成员,是Java HotSpot VM 项目下, Garbage First Garbage Collector 的性能负责人。
在性能和架构领域具有10年以上的工作经验。
在Oracle和Sun Microsystems之前的工作,Monica 负责 Spansion Inc.的性能调优工作。
Monica与许多基于Java的性能测试标准进行了合作, 致力于探寻 Java HotSpot VM 的性能改进。

相关资源和链接

  • G1生产环境参数调优-EN

  • 垃圾优先型垃圾回收器调优-官方的机器翻译中文版

  • G1垃圾收集器调优-英文版

  • JavaSE官方文档目录-英文版

  • G1特性简介-英文版

  • HotSpot垃圾收集器简介-英文版

  • Java Hotspot G1 GC的一些关键技术

  • Understanding G1 GC Logs

  • Facebook: I Love Java

  • Twitter: Java

  • Java Blog

深入解析G1垃圾收集器与性能优化相关推荐

  1. JVM性能调优实践:G1 垃圾收集器介绍篇

    前言 前面两篇主要整理了性能测试的主要观察指标信息:性能测试篇,以及JVM性能调优的工具:JVM篇.这一篇先简单总结一下GC的种类,然后侧重总结下G1(Garbage-First)垃圾收集器的分代,结 ...

  2. JVM性能调优实践——G1 垃圾收集器分析、调优篇

    前言 关于G1 GC以及其他垃圾收集器的介绍可以参考前一篇JVM性能调优实践--G1 垃圾收集器介绍篇.了解了G1垃圾收集器的运行机制之后,就可以针对一些GC相关参数来调整内存分配以及运行策略.下文的 ...

  3. java g1垃圾收集器优化参考

    1. G1适用场景 G1从jdk7开始,jdk9被设为默认垃圾收集器:在jdk8就需要指定参数配置,目标就是彻底替换掉CMS. G1的首要目标是为需要大量内存的系统提供一个保证GC低延迟的解决方案,也 ...

  4. JVM优化系列-JVM G1 垃圾收集器

    导语   G1回收器是在JDK1.7中正式使用的一种全新的垃圾回收器,它的目标是为了取代CMS回收器.G1回收器拥有独特的垃圾回收策略,和之前的任意的一种垃圾回收器都有所不同,但是从分代策略上来说依然 ...

  5. G1垃圾收集器全视角解析

    本文来说下G1垃圾收集器 文章目录 概述 GC的分类 串行垃圾回收器 并行垃圾回收器 并发标记扫描垃圾回收器(CMS) G1垃圾收集器 G1垃圾收集器详解 G1分区的概念 G1 中的重要数据结构和算法 ...

  6. G1 垃圾收集器原理详解

    一.G1 垃圾收集器的开发背景: 1.CMS 垃圾收集器的缺陷: JVM 团队设计出 G1 收集器的目的就是取代 CMS 收集器,因为 CMS 收集器在很多场景下存在诸多问题,缺陷暴露无遗,具体如下: ...

  7. 详解 JVM Garbage First(G1) 垃圾收集器

    前言 Garbage First(G1)是垃圾收集领域的最新成果,同时也是HotSpot在JVM上力推的垃圾收集器,并赋予取代CMS的使命.如果使用Java 8/9,那么有很大可能希望对G1收集器进行 ...

  8. JVM G1垃圾收集器

    Garbage-First(后文简称G1)收集器是当今收集器技术发展的最前沿成果,在Sun公司给出的JDK RoadMap里面,它被视作JDK 7的HotSpot VM 的一项重要进化特征.从JDK ...

  9. java9 g1垃圾收集器_Java 9中默认为G1垃圾收集器的情况

    java9 g1垃圾收集器 在前面的几篇文章中,我已经在InfoQ上介绍并讨论了"垃圾第一垃圾收集器" -G1:一个由所有垃圾收集器来统治它们以及调整垃圾第一垃圾收集器的技巧 . ...

最新文章

  1. python数字类型-详解python的数字类型变量与其方法
  2. js学习笔记——在html中嵌入脚本
  3. html怎么让动画最后消失了,css3 – 不能停止css动画在最后一个关键帧后消失
  4. 学会了CopyOnWriteArrayList可以再多和面试官对线三分钟
  5. Mybatis中的updateByPrimaryKeySelective()和updateByPrimaryKey()
  6. 上传文件到服务器地址怎么配置,文件上传到服务器怎么配置
  7. Spring中IOC和AOP的详细解释(转)
  8. Android 自定义View:教你轻松实现内存清理加速球的效果
  9. Endnote快速上手
  10. 搜狐股票接口获取数据方法
  11. 拼接大屏数据展示_大屏幕实时数据可视化解决方案?
  12. firefox火狐同步windows和linux书签
  13. OpenStack私有云安装配置虚拟机
  14. 学渣的刷题之旅 leetcode刷题 3. 无重复字符的最长子串(暴力法、滑动窗口)
  15. 周转时间,平均周转时间,带权周转时间
  16. 蜂云软件-会员管理系统的详细功能
  17. kali默认密码toor/kali
  18. 互联网公司产品经理要求及需要掌握的技能
  19. cesium着色器学习系列2-Appearance对象
  20. HTML基础之label标签

热门文章

  1. 蓝桥杯c语言试题寒假作业,寒假作业--蓝桥杯
  2. 记一次笔记本SSD系统迁移
  3. Git版本工具系列之一:Git常用命令
  4. Pycharm安装GDAL
  5. 吐血总结:解决 Reboot and select proper boot device or ......以及其它蓝屏黑屏
  6. My Life, Rated!
  7. yii2框架-yii2的组件和服务定位器(四)
  8. Ubuntu下修改文件夹的所有者权限
  9. 苹果工具条_苹果iOS 13.4大版本更新正式推送!新功能+新表情登场
  10. 【娱乐大闯关】C语言实现模拟ATM机管理系统