本文翻译自Getting Started with G1 Gabage Collector部分章节。并未一字一句照译。同时也根据文尾的参考文档,适当增加了部分内容
以下章节省略 Purpose、Time to Complete、Introduction、Hardware and Software Requirements、Prerequisites、Java Overview、Java Runtime Edition、Java Programming Language、Java Development Kit、Java Virtual Machine、Reviewing Generational GC and CMS、CMS Collection Phases、Reviewing Garbage Collection Steps、Basic Command Line、Key Command Line Switches、Logging GC with G1、summary
Hotspot JVM架构有着非常强大的特性和能力,能够支持做到高性能并且大规模可扩展,比如:Hotspot JVM的JIT 编译器会动态的做优化,换句话说,这些优化即在Java应用在运行时,它会针对当前系统的架构产生高性能的本地机器指令。另外,经过成熟演化和持续的对运行时环境、多线程垃圾回收的工程改造,Hotspot JVM甚至可以在最大的现存计算机系统仍然保持着高扩展性
JVM的主要组件包括类加载、运行时区域和执行引擎
Hotspot能够做到高性能的关键组件如下图
JVM提升性能主要在于3个组件,堆是存储对象的地方,它就是垃圾回收器选中要管理的地方,大多数情况下,我们只需要调整堆的大小以及选择合适的垃圾回收器。JIT编译器也会对性能产生很大的影响,只是一般是在更新版本的JVM中做调整
一般讲到Java应用,主要关注两件事情:响应和吞吐量
响应描述的是系统返回数据时快不快,比如:
吞吐量聚焦于在一段时间内最大化系统处理的任务,可以按照如下方式来衡量吞吐量:
对于关注吞吐量的系统来说长时间见的暂停是可以接受的(关注的是一段时间内,因此快速响应并不在考虑内)
G1(Garbage-First)是被设计来处理多核、大内存机器的服务端垃圾回收器,它在保持高吞吐量的前提下尽可能达到目标暂停时间。它从JDK 7 update 4之后开始完全支持,它被设计成:
G1会作为CMS的一个长期替代品,G1跟CMS相比,G1的一些差别使得它是一个更好的选择。
老一代的垃圾回收器把堆分成了三块:年轻代、年老代以及拥有一块固定内存大小的永久带
所有内存对象比在这三块区域中的一个,G1则不同:
执行垃圾回收时,G1首先进行concurrent global marking,完成后就知道哪些Region回收后基本是空的。回收时首先回收能够产生大量空间的Region(这也就是为什么会被叫做G1回收器),压缩也是一样,G1使用暂停预测模型来达到用于定义的预期暂停时间,然后根据目标时间选定几个Region来进行回收。
被选定为可垃圾回收的Region会通过evacuation来回收,G1会从多个Region中拷贝对象到堆里面的一个Region,也就是一边压缩一半释放内存。evacuation是在多核中并行进行的,以便减少暂停时间增加吞吐量。通过这么操作,每次垃圾回收,G1都能减少碎片,同时还在用户定义的暂停时间之内(其它的垃圾回收器则做不到)。
G1 不是一个实时的回收器,它会尽可能的达到定义的目标暂停时间,但不是一定。根据上一次的回收数据,G1能够预测在用户定义的时间内能回收多少的区域(通过这些数据也就能做出预估模型,把模型又作用于回收过程中)
注意 G1同时有并发(和应用线程一起执行,比如细化、标记、清理)和并行(多线程,比如暂停应用(stop the word))阶段。full GC仍然是单线程,如果有可能应用程序要尽量避免
如果从ParallelOldGC或者CMS迁移到G1,很有可能会使用更大的进程空间,这很有可能和Remembered Sets和Collection Sets相关:
G1是被设计成处理大内存同时兼顾优先的GC延迟的垃圾回收器。也就是是说,堆的大小最好大于等于6G,暂停时间小于0.5秒,当旧的GC出现如下特征可以考虑迁移:
实际上除了上图所示的eden、survivor和old 之外,还有一种:大对象Region(Humongous regions),它被设计成存储那些超过单个Region大小50%对象,大于1个region则会使用连续的region来存储。
注意这些区域不必像那些老的垃圾回收器那样连续
这是一次STW(stop the word)暂停,eden和survivor的大小会计算出来,以便下一次young gc
总的来说,G1中的young gc可以概括如下
G1 old 年代会执行下述步骤(注意部分阶段是年轻代回收的一部分)
阶段 | 描述 |
---|---|
(1)初始标记 (STW) | 这是一个STW的事件,它搭载在正常的young gc上,标记有可能引用了老年代的survivor region(root region) |
(2)根region扫描 | 扫描survivor region找到有老年代的引用,此时应用程序仍在运行,这个阶段必须在young gc发生之前完成 |
(3)并行标记 | 找到整个堆中存活的对象,此时应用程序仍然在运行,这个阶段可以被年轻代垃圾回收中断 |
(4)重新标记(STW) | 完成堆中存活对象的标记,使用snapshot-at-the-beginning(SATB)算法 |
(5)清除(STW和并发) | 记录存活对象和完全空闲的region(STW);清洗RSet(STW);重置空的regoin,归还到空闲列表(并发) |
(*)拷贝(STW) | 拷贝(或者疏散)活着的对象到没有使用过的region,在young gc中使用 [GC pause (young)]表示,在young和old同时gc的时候使用[GC pause (mixed)] |
2. 并行标记。如果发现了空的region(图中用X标记),他们会在remark阶段立马清除,另外也会记下存活的信息
3. 重新标记阶段。空的region马上被移除回收,region的存活信息此刻进行计算
4. 拷贝/清除阶段。G1会选择存活最少的区域,这些区域能够更快的回收,这些区域会在young gc发生的时候一起回收,在日志中可以看到 [GC pause (mixed)],如此,年轻和年老代就同时回收了
5. 拷贝/清除之后的阶段。被选中的区域会回收并压缩到深蓝色和深绿色的region,如下图所示
参数与默认值 | 描述 |
---|---|
-XX:+UseG1GC | 使用G1垃圾回收器 |
-XX:MaxGCPauseMillis=n | gc目标最大暂停时间,默认200毫秒 |
-XX:InitiatingHeapOccupancyPercent=n | 当old区占据整个堆空间的大小达到这个比例时才开始并发GC标记,默认值是45%,0表示做常数次的GC周期(简称IHOP) |
-XX:NewRatio=n | new/old的比例,默认是2 |
-XX:SurvivorRatio=n | eden/survivor的比例,默认是8 |
-XX:MaxTenuringThreshold=n | 寿命阈值最大值,默认15 |
-XX:ParallelGCThreads=n | 并行GC阶段使用的线程数,默认值和JVM跑的平台相关 |
-XX:ConcGCThreads=n | 并发GC阶段使用的线程数,默认值和JVM跑的平台有关 |
-XX:G1ReservePercent=n | 保留内存的空闲比例,以便减少空间溢出的风险,默认是10% |
-XX:G1HeapRegionSize=n | region大小,最小值1M,最大32M |
-XX:G1HeapWastePercent=10 | 当垃圾回收比例低于这个值时,不发生mixed GC,默认是10 |
-XX:G1OldCSetRegionThresholdPercent=10 | mixed gc过程中能回收的年老代region上限 |
-XX:G1MixedGCLiveThresholdPercent=65 | 年老代存活对象的比例,在此比例之下才会被选入CSet |
Getting Started with G1 Gabage Collector Garbage First Garbage Collector Tuning