Garbage-First 详解(上)

简介: 本文主要讲述 G1 垃圾收集器的结构、垃圾收集器的处理步骤、垃圾回收的分类、常见的参数设置、以及使用场景介绍和使用建议几个步骤来进行分开介绍本文主要是基于 openjdk-1.8 为基础展开

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作为老年代的一部分来进行看待,如图所示。


image.png


虽然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 采用复制算法回收不会有太多内存碎片


image.png


G1 收集器在后台维护了一个优先级列表,每次根据允许的收集时间,优先选择回收价值最大的 Region (这个也是它的名字 Garbage-First 的由来)比如 Region 花 200ms 能回收 10M 垃圾, 另外一个 Region 花 50ms 能够回收 20M 垃圾,在回收时间有限的情况下, G1 当然会有限选择后面这个 Region 回收。 这种使用 Region 划分内存空间以及优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能的提高垃圾回收效率。



相关文章
|
1月前
|
存储 算法 Java
Garbage First收集器(简称G1)
本文详细介绍了G1垃圾收集器的设计理念、基于Region的内存布局、大对象处理、记忆集的复杂应用、并发标记阶段的挑战以及可靠的停顿预测模型。
37 0
Garbage First收集器(简称G1)
|
6月前
|
算法 Java
Java内存管理,什么是垃圾回收机制(Garbage Collection)?
Java内存管理,什么是垃圾回收机制(Garbage Collection)?
45 1
|
算法 Java
JVM-06垃圾收集Garbage Collection(下)【垃圾收集器】
JVM-06垃圾收集Garbage Collection(下)【垃圾收集器】
77 0
|
算法 Java
JVM-05垃圾收集Garbage Collection(中)【垃圾收集算法】
JVM-05垃圾收集Garbage Collection(中)【垃圾收集算法】
69 0
|
监控 数据可视化 Java
JVM-07垃圾收集Garbage Collection【GC日志分析】
JVM-07垃圾收集Garbage Collection【GC日志分析】
154 0
|
Java
JVM-08垃圾收集Garbage Collection【GC常用参数】
JVM-08垃圾收集Garbage Collection【GC常用参数】
63 0
|
算法 Java 程序员
【GC 垃圾收集器】
【GC 垃圾收集器】
|
算法 Java 程序员
|
Java
GC是什么? 为什么要有GC?
GC是垃圾收集的意思(Gabage Collection)
189 0
|
Java 程序员 Android开发
GC是什么?为什么要有GC?
GC是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。
2311 0