转载

一文理解G1收集器

G1简介

Garbage First Collector, 简称G1 Collector,是HotspotJDK1.7后提供的面向大内存(Heap区数G到数10G)、多核系统的收集器,能够实现软停顿目标收集并且具有高吞吐量, 具有更可预测的停顿时间。

G1是一种并发、并行、部分Stop The World、使用Copying算法收集的分代的增量式收集器,

G1的全堆的操作,像global marking,是和应用(mutator)并发执行的,这样可以减少对mutator的暂停时间。清除阶段则使用多线程来提高吞吐量。

与Hotspot之前的Serial、Parallel、CMS等收集器不同的是,G1将堆分为很多大小相等的Region, 每次收集时会判断各个Region的活性-即垃圾对象的占比,垃圾对象占比越多的Region回收的收益越大,然后G1会按照设置的停顿时间目标、前几次回收Region所用时间来估算要回收哪些Region,即用最小的时间获取最大的收益,这也是Garbage First名字的含义。

Garbage First Collector的使命是在未来替换CMS,并且在JDK1.9已经成为默认的收集器。

为什么需要G1

Hotspot之前已经携带了Serial, Paralel, CMS等收集器,为什么还需要研发一个新的G1呢?垃圾收集的三个性能指标: footprint, max pause time, throughput似乎像CAP一样不能同时满足。

在服务端更注重的是短停顿时间,也就是stop-the-world的时间,另外一段时间内的总停顿时间也是一个衡量指标。

Mark-Sweep, Mark-Compact均需要和清理区域大小成比例的工作量,而Copying算法则需要一般是一半的空间用于存放每次copy的活对象。CMS的Initial Marking和Remarking两个STW阶段在Heap区越来越大的情况下需要的时间越长,并且由于内存碎片,需要压缩的话也会造成较长停顿时间。所以需要一种高吞吐量的短暂停时间的收集器,而不管堆内存多大。

G1的实现方式

Region

避免长暂停时间,可以考虑将堆分成多个部分,一次收集其中一部分,这样的方式又叫做增量收集(incremental collection), 分代收集也可以看成一种特殊的增量收集。

G1收集器将堆内存划分为一系列大小相等的Region区域,Region大小在1MB到32MB在启动时确定,G1同样也使用分代收集策略,将堆分为Eden, Survivior, Old等,只不过是按照逻辑划分的,每个Region逻辑上属于一个分代区域,并且在物理上不连续,当一个Old的Region收集完成后会变成新可用Region并可能成为下一个Eden Region。当申请的对象大于Region大小的一半时,会被放入一个Humongous Region(巨型区域)中。当一个Region中是空的时,称为可用Region或新Region。

一文理解G1收集器

图片中E指Eden, S是Survivor, H指Humongous, O是Old, 空白区域是可用分区。

CardTable

因为G1只回收一部分Region, 所以回收的时候需要知道哪些其他Region的对象引用着自己Region的对象,因为采用的copying算法需要移动对象,所以要更新引用为对象的新地址,在普通的分代收集中也是如此,分代收集中年轻代收集需要老年代到年轻代的引用的记录,通常叫做remembered set(简称RS)。CardTable是一种remembered set, 一个card代表一个范围的内存,目前采用512bytes表示一个card,cardtable就是一个byte数组,每个Region有自己的cardtable。维护remembered set需要mutator线程在可能修改跨Region的引用的时候通知collector, 这种方式通常叫做write barrier(和GC中的Memory Barrier不同), 每个线程都会有自己的remembered set log,相当于各自的修改的card的缓冲buffer,除此之外还有全局的buffer, mutator自己的remember set buffer满了之后会放入到全局buffer中,然后创建一个新的buffer。

一文理解G1收集器

只有来自其他Region的引用需要记录在RS中,所以Region内部的引用和null都不需要记录RS。

收集过程分类

Marking

G1收集器的标记阶段负责标记处存活的对象、并且计算各个Region的活跃度等。

G1使用了一种Snaphot-At-The-Beginning简称SATB的标记算法, 记录标记开始时的对象图的快照,之后并发收集过程中的新申请的对象都认为是存活对象, 当堆使用比例超过InitiatingHeapOccupancyPercent后开始marking阶段,使用SATB记录marking开始阶段的对象图快照,。

G1使用bitmap标记处哪些位置已经完成标记了,一个bitmap的bit表示8bytes, 我们使用两个marking bitmap,一个previous、一个next, previous marking bitmap表示已经完成标记的部分,标记完成后会交换previous和next

标记阶段分为几个步骤。

Initial Marking Phase

标记周期的最开始是清除next marking bitmap,是并发执行的。然后开始initial marking phase, 会暂停所有线程,标记出所有可以直接从GC roots可以到达的对象,这是在Young GC的暂停收集阶段顺带进行的。

Root Region Scan Phase

找出所有的GC Roots的Region, 然后从这些Region开始标记可到达的对象,是一个并发阶段。

Concurrent Marking Phase

这个阶段G1通过tracing找出整个堆所有的可到达的对象。这个阶段是并发执行的。

Remark Phase

Remark是一个STW阶段,G1将所有的SATB buffer处理完成。

Cleanup Phase

marking的最后一个阶段,G1统计各个Region的活跃性,完全没有存活对象的Region直接放入空闲可用Region列表中,然后会找出mixed GC的Region候选列表。

收集过程

和一般的分代式收集不同,G1中除了普通的Young GC,还有Mixed GC。

Young Garbage Collection

当Eden区域无法申请新的对象时(满了),就会进行Young GC, Young GC将Eden和Survivor区域的Region(称为Collection Set, CSet)中的活对象Copy到一些新Region中(即新的Survivor),当对象的GC年龄达到阈值后会Copy到Old Region中。由于采取的是Copying算法,所以就避免了内存碎片的问题,不再需要单独的压缩。

Mixed Garbage Collection

当old区Heap的对象占总Heap的比例超过InitiatingHeapOccupancyPercent之后,就会开始ConcurentMarking, 完成了Concurrent Marking后,G1会从Young GC切换到Mixed GC, 在Mixed GC中,G1可以增加若干个Old区域的Region到CSet中。

Mixed GC的次数根据候选的Old CSet和每次回收的

Full GC

和CMS一样,G1的一些收集过程是和应用程序并发执行的,所以可能还没有回收完成,是由于申请内存的速度比回收速度快,新的对象就占满了所有空间,在CMS中叫做Concurrent Mode Failure, 在G1中称为Allocation Failure,也会降级为一个STW的fullgc。

Floating Garbage

G1使用一种Snapshot-At-The-Begining的方式记录活对象,也就是那一时刻(整个堆concurrent marking开始的时候)的内存的Object graph, 但是在之后这里面的对象可能会变成Garbage, 叫做floating garbage 只能等到下一次收集回收掉。

总结

G1的特点有,将Heap分为大小相等的Region,逻辑分代,Marking的大部分是并发的,STW中大部分采取多线程并行执行,采用Copying进行多线程并行收集。

G1常见的调节参数

-Xmx -Xms

和其他收集器一样,是配置堆的最大大小和初始大小

-XX:MaxGCPauseMillis=200复制代码

GC最大暂停时间,默认200ms

-XX:InitiatingHeapOccupancyPercent=45复制代码

开始一个标记周期的堆占用比例阈值,默认45%,注意这里是整个堆,不同于CMS中的Old堆比例。

-XX:G1HeapRegionSize=n复制代码

设置每个Region的大小,这里需要是1MB到32MB的2的指数的大小。

推荐使用G1的场景

个人认为更换GC或者进行调优只能算是系统的锦上添花,并不能作为主要解决系统性能问题的关键,出现内存问题时,应当以修改应用代码为主、编写清晰的GC友好的代码,选择与应用场景合适的收集器可以提高系统的性能。

现在推荐从CMS更换到G1的一些情况如下:

  • Java堆的50%以上都是活对象
  • 对象的分配速率变化很大
  • 由于old gc或压缩导致不可忍受的长时间的暂停
原文  https://juejin.im/post/5d8acc78f265da5b8d18b600
正文到此结束
Loading...