引言:G1垃圾收集器采用一个略微不同的手段来解决并行、串行以及CMS GC的众多缺陷。对于大的Java堆来说,通过将Java堆拆分成一个个分区,G1会比其他垃圾收集器有更好的综合表现。
本文选自《Java性能调优指南》。
G1垃圾收集器采用一个略微不同的手段来解决并行、串行以及CMS GC的众多缺陷。G1将堆拆成一系列的分区,这样在一个时间段内,大部分的垃圾收集操作就只是在一个分区内执行,而不是整个堆或整个(老年)代。
在G1里,年轻代就是一系列的内存分区,这意味着不用再要求年轻代是一个连续的内存块。类似地,老年代同样也是由一系列的分区组成。这样也就不需要在JVM运行时考虑哪些分区是老年代,哪些是年轻代。事实上,G1通常的运行状态是映射G1分区的虚拟内存随着时间的推移在不同的代之间前后切换。一个G1分区最初被指定为年轻代,经过一次年轻代的回收之后,整个年轻代分区都被划入到未被使用的分区中,那它也就可以被使用在别的地方了。
一个可用分区能被用于或指定为年轻代或老年代分区。很可能在完成一个年轻代收集之后,一个年轻代的分区在未来的某个时刻被用于老年代分区。同样地,在一个老年代分区完成收集之后,它就成为了可用分区,在未来某个时候作为一个年轻代分区来使用。
G1年轻代的收集方式是并行stop-the-world。在垃圾收集线程执行过程中,并行stop-the-world回收将暂停所有Java应用线程,而垃圾回收的工作也将通过多个线程来分担。与其他HotSpot垃圾收集器一样,一旦发生一次年轻代垃圾收集,整个年轻代都会被回收。
而G1老年代的垃圾收集方式与其他HotSpot垃圾收集器有着极大的不同。G1老年代的收集不会为了释放老年代的空间就要求对整个老年代做回收。相反,在任一时刻只有一部分老年代分区会被回收,并且,这部分老年代分区将与一次年轻代收集一起被收集。
贴士
术语混合(mixed)垃圾收集就是用来描述这种一部分老年代分区与年轻代收集结合在一起进行的收集。因此,混合GC就是在一次垃圾收集事件中,所有年轻代分区以及一部分老年代分区将会被回收。换句话说,混合GC就是将要被回收的年轻代与年老代分区的组合。
与CMS GC类似,当遇到一些极端情况时,诸如老年代空间被消耗完了,会有一个安全措施来收集和压缩整个老年代。
撇开安全模式下的收集不谈,一个G1老年代收集是由一系列阶段组成,某些是并行stop-the-world的,某些是并行并发的。也就是说,某些阶段是多线程的同时会暂停所有应用线程,而其他阶段是多线程的,但可以与应用线程同时运行。
当超过Java堆的占用阈值,G1就会启动一次老年代收集。要注意到有一点非常重要,那就是G1中的堆占用阈值,这是根据老年代占用空间与整个Java堆空间相比较得出的。熟悉CMS GC的读者会记得,CMS触发老年代收集所用的占用阈值只是相对于老年代空间本身而言的。在G1中,一旦达到或超过内存堆的占用阈值,一次并发stop-the-world方式的初始标记阶段就会被安排执行。
初始标记阶段会跟着下一次年轻代收集同时进行。一旦初始标记阶段结束,就会触发一个并发多线程的标记阶段,标记老年代中所有的存活对象。当并发标记阶段结束,并行stop-the-world的重新标记阶段就被启动,标记那些因为在标记阶段同时执行的应用线程导致产生错过的对象。到重新标记阶段结束,G1就拥有了老年代分区的完整信息。如果碰巧老年代分区里一个存活对象都没有,那么在下一个阶段——清除阶段,不用做额外的垃圾收集工作就可以被回收再利用。
同样也是在重新标记阶段结束,G1能识别出最适合回收的老年代分区集合。
贴士
在垃圾收集过程中收集的分区集合可以称为收集集合(CSet)。
选择哪些分区被包含在一个CSet里,是基于有多少空间可以被释放以及G1暂停时间目标。在完成CSet识别之后,G1就在接下来的几次年轻代垃圾收集过程中对CSet中的分区进行回收。也就是说,在接下来的几个年轻代垃圾收集中,除了年轻代分区,还有一部分的老年代分区也将被回收。这就是前面提到的垃圾收集事件中的混合GC类型。
不管是年轻代还是老年代,G1会把每个收集过垃圾的分区中的存活对象转移到一个可用分区中。一旦存活对象被转移掉,那这个年轻代分区(可能还有老年代分区)就会被回收为可用分区。
将各老年代分区中的存活对象转移到可用分区会带来一个很有吸引力的结果——在虚拟地址空间里每个转移对象都是前后相连的,对象和对象之间没有碎片化的空余空间。我们回忆一下,CMS、并行以及串行垃圾收集器都需要一个full GC来压缩老年代,而这个压缩动作需要扫描整个老年代空间。
因为G1以每个分区为基础做垃圾收集操作,所以它适用于大尺寸的Java堆。垃圾收集工作的数量可以被限制在一个小范围的分区集合内,哪怕Java堆尺寸可能会相当大。
G1暂停时间的最大来源是年轻代收集和混合收集,所以G1的设计目标之一就是允许用户设置GC暂停时间目标。G1会尝试通过调整Java堆尺寸大小的方式来满足设定的暂停时间目标。它会根据暂停时间目标自动调整年轻代的尺寸和总的Java堆尺寸。暂停时间目标越短,年轻代空间就越小,总的堆空间就越大,使得老年代空间相对就越大。
G1的设计目标就是把必要的调整限定在设置最大的Java堆空间和指定GC暂停时间目标上。另外,G1还被设计成可以通过一个内部的启发式算法来做自我调整。在写这本书的时候,G1的启发式算法就是HotSpot GC开发活动开展的最活跃的地方。同样到写这本书时为止,G1也许会要求一些特定场景下的额外调优,不过开发优秀的启发式算法的先决条件已经具备并且前景很可观。至于如何对G1进行调优的建议。
综上,对于大的Java堆来说,通过将Java堆拆分成一个个分区,G1会比其他垃圾收集器有更好的综合表现。在局部压缩的帮助下,G1解决了Java堆碎片,它的绝大部分工作都通过多线程的方式完成。
截止到写这本文时,G1首要针对的是那些有合理短暂停的大尺寸Java堆的用例,当然还有那些正在使用CMS垃圾收集器的应用。也有计划用G1来处理吞吐量用例,但对于那些追求高吞吐量,同时能容忍更长GC暂停的应用来说,目前并行垃圾收集器是更好的选择。
本文选自《Java性能调优指南》,点此链接可在博文视点官网查看此书。
想及时获得更多精彩文章,可在微信中搜索“博文视点”或者扫描下方二维码并关注。