G1 垃圾收集器
G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要是针对匹配多核心处理器以及大容量内存的机器,以极高的概率满足 GC 停顿时间要求的需求,还具备高吞吐量的特征。
G1 垃圾收集器结构
G1 是基于 Region 的堆内存布局是它能够实现这个目标的关键。虽然 G1也仍是遵循分代收集理论设计的,但其堆内存的布局与其他收集器有非常明显的差异:G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的 Region 采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。
Region中还有一类特殊的 Humongous 区域,专门用来存储大对象。G1认为只要大小超过了一个 Region 容量一半的对象即可判定为大对象。**每个 Region 的大小可以通过参数 -XX:G1HeapRegionSize 设定,取值范围为 1MB~32MB,且应为 2 的 N 次幂。
**相关的设置代码如下:
// Minimum region size; we won't go lower than that. // We might want to decrease this in the future, to deal with small // heaps a bit more efficiently. #define MIN_REGION_SIZE ( 1024 * 1024 ) // Maximum region size; we don't go higher than that. There's a good // reason for having an upper bound. We don't want regions to get too // large, otherwise cleanup's effectiveness would decrease as there // will be fewer opportunities to find totally empty regions after // marking. #define MAX_REGION_SIZE ( 32 * 1024 * 1024 ) // The automatic region size calculation will try to have around this // many regions in the heap (based on the min heap size). #define TARGET_REGION_NUMBER 2048 size_t HeapRegion::max_region_size() { return (size_t)MAX_REGION_SIZE; } void HeapRegion::setup_heap_region_size(size_t initial_heap_size, size_t max_heap_size) { uintx region_size = G1HeapRegionSize; if (FLAG_IS_DEFAULT(G1HeapRegionSize)) { size_t average_heap_size = (initial_heap_size + max_heap_size) / 2; region_size = MAX2(average_heap_size / TARGET_REGION_NUMBER, (uintx) MIN_REGION_SIZE); } int region_size_log = log2_long((jlong) region_size); // Recalculate the region size to make sure it's a power of // 2. This means that region_size is the largest power of 2 that's // <= what we've calculated so far. region_size = ((uintx)1 << region_size_log); // Now make sure that we don't go over or under our limits. if (region_size < MIN_REGION_SIZE) { region_size = MIN_REGION_SIZE; } else if (region_size > MAX_REGION_SIZE) { region_size = MAX_REGION_SIZE; } // And recalculate the log. region_size_log = log2_long((jlong) region_size); // Now, set up the globals. guarantee(LogOfHRGrainBytes == 0, "we should only set it once"); LogOfHRGrainBytes = region_size_log; guarantee(LogOfHRGrainWords == 0, "we should only set it once"); LogOfHRGrainWords = LogOfHRGrainBytes - LogHeapWordSize; guarantee(GrainBytes == 0, "we should only set it once"); // The cast to int is safe, given that we've bounded region_size by // MIN_REGION_SIZE and MAX_REGION_SIZE. GrainBytes = (size_t)region_size; guarantee(GrainWords == 0, "we should only set it once"); GrainWords = GrainBytes >> LogHeapWordSize; guarantee((size_t) 1 << LogOfHRGrainWords == GrainWords, "sanity"); guarantee(CardsPerRegion == 0, "we should only set it once"); CardsPerRegion = GrainBytes >> CardTableModRefBS::card_shift; }
而对于那些超过了整个 Region 容量的超级大对象, 将会被存放在 N 个连续的 Humongous Region 之中,G1的大多数行为都把 Humongous Region作为老年代的一部分来进行看待,如图所示。
虽然G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,它们都是一系列区域(不需要连续)的动态集合。G1收集器之所以能建立可预测的停顿时间模型,是因为它将 Region 作为单次回收的最小单元,即每次收集到的内存空间都是 Region 大小的整数倍,这样可以有计划地避免在整个 Java 堆中进行全区域的垃圾收集。更具体的处理思路是让 G1 收集器去跟踪各个 Region 里面的垃圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间(使用参数-XX:MaxGCPauseMillis指定,默认值是200毫秒),优先处理回收价值收益最大的那些 Region,这也就是“Garbage First”名字的由来。这种使用Region划分内存空间,以及具有优先级的区域回收方式,保证了G1收集器在有限的时间内获取尽可能高的收集效率。
G1 垃圾收集器具备以下特点:
- 并行与并发:G1能充分利用多 CPU、多核环境下的硬件优势,使用多个CPU来缩短 Stop-the-world停 顿的时间,部分其他收集器原来需要停顿 Java 线程执行的 GC 操作,G1 收集器仍然可以通过并发的方式让 Java 程序继续运行。
- 分代收集:虽然 G1 可以不需要其他收集器配合独立管理整个 GC 堆,但是还是保留了分代的概念。
- 空间整合:与CMS的标记-清除算法不同,G1从整体来看是基于标记-整理算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的。但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次 GC。
- 可预测的停顿:这是 G1 相对于 CMS 的一个优势,降低停顿时间是 G1和 CMS 共同的关注点, 但 G1 除了追求低停顿之外,还建立 可预测的停顿时间模型, 能让使用者明确指定在一个长度为 M 毫秒的时间片段完成垃圾收集。
G1 收集步骤
- 初始标记(Initial Marking , STW):仅仅只是标记一下 GC Roots 能直接关联到的对象,并且修改 TAMS 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的 Region 中分配新对象。**这个阶段需要停顿线程,但耗时很短,而且是借用进行 Minor GC 的时候同步完成的,所以 G1收集器在这个阶段实际并没有额外的停顿。 **
- 并发标记(Concurrent Marking):从 GC Root 开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以 后,还要重新处理 SATB 记录下的在并发时有引用变动的对象。
- 最终标记(Final Marking, STW):对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的 SATB 记录。
- 筛选回收(Live Data Counting and Evacuation, STW):负责更新Region的统计数据,对各个Region的回收价值和成本进行排序(设置JVM参数:-XX:MaxGCPausemillis 设定),根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region 构成回收集,然后把决定回收的那一部分Region 的存活对象复制到空的 Region 中,再清理掉整个旧 Region 的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。回收算法主要是采用复制算法,将一个 region 复制存活对象到其他 region 中, 这种不会像 CMS 那样一次回收后会有很多内存碎片需要整理一次, G1 采用复制算法回收不会有太多内存碎片
G1 收集器在后台维护了一个优先级列表,每次根据允许的收集时间,优先选择回收价值最大的 Region (这个也是它的名字 Garbage-First 的由来)比如 Region 花 200ms 能回收 10M 垃圾, 另外一个 Region 花 50ms 能够回收 20M 垃圾,在回收时间有限的情况下, G1 当然会有限选择后面这个 Region 回收。 这种使用 Region 划分内存空间以及优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能的提高垃圾回收效率。