入门G1垃圾回收器

·  阅读 366

本文翻译自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架构

Hotspot JVM架构有着非常强大的特性和能力,能够支持做到高性能并且大规模可扩展,比如:Hotspot JVM的JIT 编译器会动态的做优化,换句话说,这些优化即在Java应用在运行时,它会针对当前系统的架构产生高性能的本地机器指令。另外,经过成熟演化和持续的对运行时环境、多线程垃圾回收的工程改造,Hotspot JVM甚至可以在最大的现存计算机系统仍然保持着高扩展性

image.png

JVM的主要组件包括类加载、运行时区域和执行引擎

Hotspot关键组件

Hotspot能够做到高性能的关键组件如下图

image.png JVM提升性能主要在于3个组件,堆是存储对象的地方,它就是垃圾回收器选中要管理的地方,大多数情况下,我们只需要调整堆的大小以及选择合适的垃圾回收器。JIT编译器也会对性能产生很大的影响,只是一般是在更新版本的JVM中做调整

性能基础知识

一般讲到Java应用,主要关注两件事情:响应和吞吐量

响应

响应描述的是系统返回数据时快不快,比如:

  • 桌面UI对一个事件的反应有多快?
  • 网站返回一个页面有多快?
  • 数据库返回结果有多快?

对于关注响应的系统,长时间的暂停是不可接受的

吞吐量

吞吐量聚焦于在一段时间内最大化系统处理的任务,可以按照如下方式来衡量吞吐量:

  • 在一段时间内完成的事务数
  • 1小时内一个批量程序能完成的任务数
  • 1小时内数据库能完成的查询量

对于关注吞吐量的系统来说长时间见的暂停是可以接受的(关注的是一段时间内,因此快速响应并不在考虑内)

G1垃圾回收器

G1(Garbage-First)是被设计来处理多核、大内存机器的服务端垃圾回收器,它在保持高吞吐量的前提下尽可能达到目标暂停时间。它从JDK 7 update 4之后开始完全支持,它被设计成:

  • 能像CMS收集器一样和应用线程并发的执行
  • 空间紧凑,免去了冗长的GC暂停时间
  • GC暂停时间可预测
  • 不想牺牲吞吐量
  • 不需要更大的Java堆

G1会作为CMS的一个长期替代品,G1跟CMS相比,G1的一些差别使得它是一个更好的选择。

  • 一是G1是一个压缩的收集器,G1的压缩使得可以完全避免使用一个可以在细粒度分配的可用空间列表,而是使用Region,这种设计不但简化了收集器,而且消除了潜在的碎片问题;
  • 另一个是G1能让用户设置目标回收暂停时间

G1操作概述

老一代的垃圾回收器把堆分成了三块:年轻代、年老代以及拥有一块固定内存大小的永久带 image.png

所有内存对象比在这三块区域中的一个,G1则不同: image.png

  • 堆被分成了等大的Region,每个Region都有用连续的虚拟内存,部分Region的集合像老的回收器一样分配给eden、survivor、old,但是这三者是没有固定大小的,这样使得内存使用上拥有更佳的灵活性

  • 标有E的region(图中的正方形小块)表示eden空间,S表示survivor空间,O表示年老代空间,灰色的是堆中没有使用的区域。

执行垃圾回收时,G1首先进行concurrent global marking,完成后就知道哪些Region回收后基本是空的。回收时首先回收能够产生大量空间的Region(这也就是为什么会被叫做G1回收器),压缩也是一样,G1使用暂停预测模型来达到用于定义的预期暂停时间,然后根据目标时间选定几个Region来进行回收。

被选定为可垃圾回收的Region会通过evacuation来回收,G1会从多个Region中拷贝对象到堆里面的一个Region,也就是一边压缩一半释放内存。evacuation是在多核中并行进行的,以便减少暂停时间增加吞吐量。通过这么操作,每次垃圾回收,G1都能减少碎片,同时还在用户定义的暂停时间之内(其它的垃圾回收器则做不到)。

G1 不是一个实时的回收器,它会尽可能的达到定义的目标暂停时间,但不是一定。根据上一次的回收数据,G1能够预测在用户定义的时间内能回收多少的区域(通过这些数据也就能做出预估模型,把模型又作用于回收过程中)

注意 G1同时有并发(和应用线程一起执行,比如细化、标记、清理)和并行(多线程,比如暂停应用(stop the word))阶段。full GC仍然是单线程,如果有可能应用程序要尽量避免

G1脚印

如果从ParallelOldGC或者CMS迁移到G1,很有可能会使用更大的进程空间,这很有可能和Remembered Sets和Collection Sets相关:

  • Remembered Sets:又称RSets,用来记录指向Region的引用,每个Region都有一个RSet,RSet使得独自并且并行回收每一个Region成为可能。整个RSet的大小造成的影响是小于5%
  • Collection Sets: 又称CSets,用来记录在可以被回收的Region,在GC过程中,所有CSet中的存活对象都会被疏散(evacuation (拷贝或删除)),CSet中的Region可以是eden、survivor、old这三块都有。整个CSet对JVM空间影响小于1%

使用G1的建议

G1是被设计成处理大内存同时兼顾优先的GC延迟的垃圾回收器。也就是是说,堆的大小最好大于等于6G,暂停时间小于0.5秒,当旧的GC出现如下特征可以考虑迁移:

  1. Full GC时间太长或者太频繁
  2. 对象分配或晋升速率差异显著
  3. 不希望长时间的GC或者压缩暂停(超过一定阈值,比如0.5秒)

一步一步看G1 young Gc

  1. G1堆结构。堆被分成了多个固定大小的Region

image.png

  • Region的大小在JVM启动的时候制定,一般情况下有2000块,每块的大小范围为 1-32M
  1. G1堆分配。Region会从逻辑上映射成eden、survivor和old 三块

image.png

  • 相同颜色的Region表示同一个角色,活着的对象会被从一个region疏散(拷贝或删除)到其它的region。

实际上除了上图所示的eden、survivor和old 之外,还有一种:大对象Region(Humongous regions),它被设计成存储那些超过单个Region大小50%对象,大于1个region则会使用连续的region来存储。

  1. G1中的年轻代

蓝色Region用来标识年老代对象,绿色区域用来标识年轻代

image.png

  • 灰色表示没有使用的内存空间
  • 浅绿色表示年轻代
  • 浅蓝色表示年老代
  • 深绿色表示最近拷贝进年轻代(图中没有)
  • 深蓝色表示最近拷贝到年老代(图中没有)

注意这些区域不必像那些老的垃圾回收器那样连续

  1. G1中发生young gc

存活的对象被疏散到(拷贝或删除)到一个或多个的survivor region,如果达到了年龄阈值,一些对象会晋升到年老代region

image.png

  • 图中的红圈表示young gc只扫描了年轻代,箭头指向第5步中,最终存活下来的对象疏散(拷贝、删除)后的地方

这是一次STW(stop the word)暂停,eden和survivor的大小会计算出来,以便下一次young gc

  1. G1中young gc的结果

存活的对象被疏散到survivor region或者old region

image.png

G1 young GC总结

总的来说,G1中的young gc可以概括如下

  • 堆是一块被拆分成多个Region的内存
  • 年轻代内存空间是一列不连续的region的组合,这使得大小调整是很简单的
  • young gc会STW,所有的应用线程都会暂停
  • young gc会使用多线程来并行完成
  • 存活对象会被拷贝到新的survivor或者old region

G1回收阶段 - 并行标记周期阶段

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)]

一步一步看 G1中的old GC

  1. 初始标记。在gc日志中可以看到 GC pause (young)(initial-mark)这样的标识

image.png 2. 并行标记。如果发现了空的region(图中用X标记),他们会在remark阶段立马清除,另外也会记下存活的信息

image.png 3. 重新标记阶段。空的region马上被移除回收,region的存活信息此刻进行计算

image.png 4. 拷贝/清除阶段。G1会选择存活最少的区域,这些区域能够更快的回收,这些区域会在young gc发生的时候一起回收,在日志中可以看到 [GC pause (mixed)],如此,年轻和年老代就同时回收了

image.png 5. 拷贝/清除之后的阶段。被选中的区域会回收并压缩到深蓝色和深绿色的region,如下图所示

image.png

G1 老年代 GC总结

  • 并行标记阶段
    • 应用运行时并行的计算存活度信息
    • 存活度是用来标识在疏散(evacuation)阶段,那些region是最佳回收的
    • 没有类似CMS的清除阶段
  • 重新标记阶段
    • 使用 snapshot-at-the-begining(SATB)算法,使得比CMS更快
    • 完全空的region在此时被回收
  • 拷贝/清除阶段
    • 年轻代和年老代一起被回收
    • 年老代是根据存活对象的数量(存活度)来选择的

G1 GC的一些参数

参数与默认值描述
-XX:+UseG1GC使用G1垃圾回收器
-XX:MaxGCPauseMillis=ngc目标最大暂停时间,默认200毫秒
-XX:InitiatingHeapOccupancyPercent=n当old区占据整个堆空间的大小达到这个比例时才开始并发GC标记,默认值是45%,0表示做常数次的GC周期(简称IHOP)
-XX:NewRatio=nnew/old的比例,默认是2
-XX:SurvivorRatio=neden/survivor的比例,默认是8
-XX:MaxTenuringThreshold=n寿命阈值最大值,默认15
-XX:ParallelGCThreads=n并行GC阶段使用的线程数,默认值和JVM跑的平台相关
-XX:ConcGCThreads=n并发GC阶段使用的线程数,默认值和JVM跑的平台有关
-XX:G1ReservePercent=n保留内存的空闲比例,以便减少空间溢出的风险,默认是10%
-XX:G1HeapRegionSize=nregion大小,最小值1M,最大32M
-XX:G1HeapWastePercent=10当垃圾回收比例低于这个值时,不发生mixed GC,默认是10
-XX:G1OldCSetRegionThresholdPercent=10mixed gc过程中能回收的年老代region上限
-XX:G1MixedGCLiveThresholdPercent=65年老代存活对象的比例,在此比例之下才会被选入CSet

一些最佳实践

  • 不要设置年轻代的大小
  • 目标暂停时间考虑设置为90%情况下所需要花费的时间

参考

Getting Started with G1 Gabage Collector
Garbage First Garbage Collector Tuning

分类:
后端
标签:
分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改