前言
G1收集器是当今收集器技术发展的最前沿成果之一,早在2004年就发表了第一篇paper,在JDK7u4中提供使用,而在JDK9中Oracle官方将G1设置为默认的垃圾收集器,以取代CMS
简介
G1收集器是一款面向服务端应用的垃圾收集器,在实现高吞吐量的同时尽可能地满足垃圾收集暂停的时间,其特点主要有:
- 并行和并发:G1能充分利用多CPU多核环境下的硬件优势来缩短STW停顿的时间,执行GC时可通过并发的方式让用户线程继续执行
- 分代收集:与其他收集器一样,分代的概念在G1中依然得以保留,但这里的分代理念只是逻辑上的,每个Region既可能是新生代也可能是老年代
- 空间整合:G1从整体来看是采用了标记—整理算法,但从局部(两个Region之间)来看是采用了复制算法,相比于CMS它不会产生内存碎片
- 可预测的停顿时间:这是G1相比于CMS的另一个大优势,G1除了追求低停顿之外还可以建立可预测停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段中消耗在垃圾收集上的时间不得超过N毫秒
G1收集器
G1收集器的内存区域划分

+XX:G1HeapRegionSize指定分区的大小(1M~32M,必须是2的幂),默认情况下将堆划分为2048个分区。
每个分区又被划分为若干个卡片(Card),每个卡片大小为512Byte,所有分区的卡片会记录在全局卡片表(Global Card Table)中,分配的对象会占用物理上连续的若干个卡片。
其中H表示该分区存储的是巨大对象(humongous object, H-obj),即内存大小≥分区内存一半的对象。H-obj有如下的特征:
- H-obj直接分配到老年代,避免反复拷贝移动而导致效率下降
- H-obj会在Global Concurrent Marking的Clean up阶段和Full GC阶段被回收
- H-obj在分配内存之前会先检查是否超过
initiating heap occupancy percent和the marking threshold(是否有预留的空间供系统正常运行),如果超过的话就启动global concurrent marking(G1的混合收集阶段),为的是提早回收防止evacuation failures和Full GC而导致的效率降低或内存溢出的问题
在串行和并行收集器中,GC通过整堆扫描来确定对象是否处于可达路径。而G1为了避免整堆扫描带来的STW时间过长,在每个分区记录了一个Remember Set(RSet),用来记录引用本分区内对象的卡片索引。当要回收该分区时,通过扫描分区的RSet来确定引用本分区的对象是否存活,进而确定本分区内对象存活情况。RSet和Card的关系如下图:

那么还有一个问题:每次的引用更新都需要同步地去维护RSet中的引用记录吗?实际上这并不是同步,当出现引用更新时会有一个写屏障(Write Barriers)来暂停引用更新步骤,首先会判断该引用是否跨区引用,若是则通过卡表(Card Table)将跨区引用的对象卡片加入到队列(dirty card queue)中,并由后台的并发优化线程(Concurrent Refinement Thread)来进行异步处理,更新RSet。
G1收集器的回收模式
Young GC
发生在年轻代的GC算法,当所有的eden region被耗尽无法申请内存时,就会触发一次young gc,执行完一次young gc之后活跃对象会被拷贝到survivor region或晋升到old region中,常用的参数有:
- -XX:G1NewSizePercent 新生代最小值,默认5%
- -XX:G1maxNewSizePercent 新生代最大值,默认60%
Mixed GC
当越来越多对象晋升到old region时,为了避免堆内存耗尽,虚拟机会触发mixed gc,该算法并不是一个old gc,除了回收整个young region之外还会回收部分old region,这部分的old region一般是选择回收效益最高的也就是垃圾最多的region,从而可以对垃圾回收的耗时进行控制。
mixed gc也有一个阈值参数:-XX:InitiatingHeapOccupancyPercent,当old region占整个堆内存大小百分比达到该阈值时就会触发一次mixed gc
Full GC
当对象内存分配速度大于mixed gc回收速度时导致老年代被填满,就会触发一次担保机制full gc,G1收集器的full gc算法就是单线程执行的serial old gc,会导致较长时间的STW,因此需要进行多次调优避免虚拟机多次触发full gc
G1收集器和CMS收集器的比较
总的来说,G1和CMS各有优劣,G1收集器在多CPU高堆内存的运行环境下的性能是要优于CMS的,这个界限大概在6~8GB之间,当堆内存低于该界限时CMS的性能就会反过来优于G1收集器。G1收集器的另一个缺点就是相比于其他收集器而言所占用的空间更多,可以理解为空间换时间。
总结
G1是一款非常优秀的垃圾收集器,不仅适合堆内存大的应用,同时也简化了调优的工作。通过主要的参数初始和最大堆空间、以及最大容忍的GC暂停目标,就能得到不错的性能;同时,我们也看到G1对内存空间的浪费较高,但通过首先收集尽可能多的垃圾(Garbage First)的设计原则,可以及时发现过期对象,从而让内存占用处于合理的水平。
最后如果大家看了觉得有帮助的就点个赞支持一下吧:)
参考:
《深入理解Java虚拟机JVM高级特性与最佳实践第2版》
coderlius:详解 JVM Garbage First(G1) 垃圾收集器
美团技术团队:Java Hotspot G1 GC的一些关键技术