G1的设计原则是"首先收集尽可能多的垃圾(Garbage First)"。因此,G1并不会等内存耗尽(串行、并行)或者快耗尽(CMS)的时候开始垃圾收集,而是在内部采用了启发式算法,在老年代找出具有高收集收益的分区进行收集。
G1采用内存分区(Region)的思路,将内存划分为一个个相等大小的内存分区,回收时则以分区为单位进行回收,存活的对象复制到另一个空闲分区中。由于都是以相等大小的分区为单位进行操作,因此G1天然就是一种压缩方案(局部压缩);
同时G1可以根据用户设置的暂停时间目标自动调整年轻代和总堆大小,暂停目标越短年轻代空间越小、总空间就越大;
G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换;
G1的收集都是STW的,但年轻代和老年代的收集界限比较模糊,采用了混合(mixed)收集的方式。即每次收集既可能只收集年轻代分区(年轻代收集),也可能在收集年轻代的同时,包含部分老年代分区(混合收集),这样即使堆内存很大时,也可以限制收集范围,从而降低停顿。
应用程序停止的其他操作会花费更多时间,比如全局标记之类的整堆操作会与应用程序并行执行。 为了使stop-the-world在空间回收方面的停顿时间缩短,G1逐步并行地进行空间回收。
G1通过跟踪以前应用程序行为的信息和垃圾收集暂停来构建相关成本的模型,从而实现可预测性。它利用这个信息来计算停顿时所做的工作量。例如,G1首先在效率最高的区域回收空间(这些区域大部分都是垃圾,因此取名为 G1)。
G1主要通过撤离来回收空间: 在选定的内存区域内找到的活动对象被复制到新的内存区域,并在处理过程中对其进行压缩。在完成疏散之后,以前被活动对象占用的空间将被应用程序重用以进行分配。
G1收集器不是实时收集器。它试图在更长的时间内以高概率实现设定的暂停时间目标,但在给定的暂停时间内并不总是绝对确定。
堆布局G1将堆划分为一组大小相同的堆区域Region,每个区域都有一个连续的虚拟内存范围,Region区域是内存分配和内存回收的单位。
在任何给定的时间,这些区域中的每一个都可以是空的(浅灰色) ,或者分配给特定的一代,年轻的或老年的。
当内存请求进入时,内存管理器分配空闲区域。内存管理器将它们分配给一个代,然后将它们作为可用空间返回给应用程序,应用程序可以将其分配给自己。
年轻代包含伊甸园区域(红色)和幸存者区域(红色带有"S")。这些区域提供了与其他收集器中的相应连续空间相同的功能,不同之处在于,在G1中,这些区域通常以非连续的模式布局在内存中。老区域(浅蓝色)组成了老年代。对于跨越多个区域的对象,老年代区域可能非常巨大(浅蓝色带"H")。
应用程序总是分配给年轻代,即伊甸园区域,但直接分配给老年代的大型对象除外。
垃圾回收周期在较高的水平上,G1收集器在两个阶段之间交替。只有年轻(young-only)阶段包含垃圾回收,这些垃圾回收会逐渐用老年代中的对象填充当前可用的内存。在空间回收阶段,除了处理年轻代的问题外,G1逐步收回老年代的空间。然后循环重新开始,只有年轻的阶段。
下面的列表详细描述了G1垃圾收集周期的各个阶段,它们之间的停顿和过渡:
纯年轻(Young-only)阶段
这个阶段从几个普通(Normal)的年轻代回收开始,将对象升级到老年代。 当老年代占有率达到一定阈值时,即初始堆占有率阈值,纯年轻(young-only)阶段和空间回收(space-reclamation)阶段开始转换。此时,G1计划一个并发启动(Concurrent Start)年轻代回收,而不是普通(Normal)的年轻代回收。
并发启动(Concurrent Start):这种类型的回收除了执行普通年轻代回收之外,还启动标记(marking)过程。
重标记(Remark):此暂停将自行确定标记,执行全局引用处理和类卸载,回收完全空的区域并清理内部数据结构。
清理(Cleanup):这个暂停决定了是否会真正进入空间回收阶段。
空间回收(Space-reclamation)阶段:这一阶段包括多个混合(Mixed)回收,除了年轻代区域,还删除老一代区域的成套活动对象。当G1认为删除更多的老年代区域不会产生足够的自由空间时,空间回收阶段就结束了。