Skip to content

g1 mark Conc_Mark

ceragon edited this page Dec 20, 2021 · 5 revisions

启动并发标记

启动标记循环的路径

下面是启动标记循环的唯一方法,且该方法会在 VM Thread 中执行,标记标记循环启动之后,唤醒 G1 Main Marker 线程, 之后的标记循环的流程管理由 G1 Main Marker 线程负责。

  1. 启动循环的直接方法
void G1CollectedHeap::do_concurrent_mark() {
    MutexLockerEx x(CGC_lock, Mutex::_no_safepoint_check_flag);
    // 只要标记循环没有处于执行中,就为真
    if (!_cm_thread->in_progress()) {
        // 设置标记循环启动
        _cm_thread->set_started();
        // 唤醒 G1 Main Marker 线程,起来干活
        CGC_lock->notify();
    }
}
  1. 下面的方法是调用 do_concurrent_mark() 的唯一方法
bool G1CollectedHeap::do_collection_pause_at_safepoint(double target_pause_time_ms) {
    // 此方法是 G1 执行 GC 转移的唯一方法,且在 STW 期间运行
    // 本次 GC 是否应该启动并发标记
    bool should_start_conc_mark = collector_state()->in_initial_mark_gc();
    if (should_start_conc_mark) {
        // 尝试启动标记循环
        do_concurrent_mark();
    }
}
  1. 下面的方法是唯一把 in_initial_mark_gc() 标记为 true 的方法
void G1Policy::initiate_conc_mark() {
    collector_state()->set_in_initial_mark_gc(true);
    collector_state()->set_initiate_conc_mark_if_possible(false);
}
  1. 下面的方法是在决定这次是否是一个附带并发标记初始化的 GC 流程,也是唯一调用了 initiate_conc_mark() 的方法。

在这部分只是展示一下启动的调用链,具体的解析在下面的内容

void G1Policy::decide_on_conc_mark_initiation() {
    // _initiate_conc_mark_if_possible 这个值必须为 true
    if (collector_state()->initiate_conc_mark_if_possible()) {
        if (!about_to_start_mixed_phase() && collector_state()->in_young_only_phase()) {
            // 首先不能在 Mix GC 中执行初始标记,其次这次 GC 必须是完全Young GC
            initiate_conc_mark();
        } else if (_g1h->is_user_requested_concurrent_full_gc(_g1h->gc_cause())) {
            // 如果是主动触发的 Full GC,也会触发标记
            initiate_conc_mark();
        }
    }
}
  1. 接着查看 decide_on_conc_mark_initiation() 的调用方,该方法是 GC 收集转移的主体方法
bool G1CollectedHeap::do_collection_pause_at_safepoint(double target_pause_time_ms) {
    if (!_cm_thread->should_terminate()) {
        // _cm_thread 对应的是 G1 Main Marker 线程,只要该线程没有被 stop,就会进来
        g1_policy()->decide_on_conc_mark_initiation();
    }
}
  1. 再次追踪调用链,查到两个方法
    1. Young Gc、Mix GC、的 GC 方法. GC Pause 触发流程
    2. Meta Space 分配空间失败调用GC的方法. 元空间 GC 触发流程

启动标记循环的必要条件

_initiate_conc_mark_if_possible

根据上面的步骤4,如果 _initiate_conc_mark_if_possible 不为真,则无法启动标记循环。以下是将该值赋值为真的位置列表

位置列表

  1. 位置一。在 STW 期间尝试在老年代分配一个大对象,且分配成功。此时如果也满足并发标记启动条件,则标记为需要启动。
HeapWord* G1CollectedHeap::attempt_allocation_at_safepoint(size_t word_size, bool expect_null_mutator_alloc_region) {
    // 是否满足一个大对象的定义
    if (!is_humongous(word_size)) {
        // 省略
    } else {
        // 尝试在老年代分配空间,分配成功则返回分配空间的首地址
        HeapWord* result = humongous_obj_allocate(word_size);
        // 第一个条件是分配成功为真,第二个条件是满足启动条件
        if (result != NULL && g1_policy()->need_to_start_conc_mark("STW humongous allocation")) {
            collector_state()->set_initiate_conc_mark_if_possible(true);
        }
        return result;
    }
}
  1. 位置二。只要当前标记循环没有开启,就强行设置为标记循环需要启动。
bool G1Policy::force_initial_mark_if_outside_cycle(GCCause::Cause gc_cause) {
    // 标记循环是否已经启动了
   bool during_cycle = _g1h->concurrent_mark()->cm_thread()->during_cycle();
   if (!during_cycle) {
      // 循环没启动的话,标记为需要启动
      collector_state()->set_initiate_conc_mark_if_possible(true);
      return true;
   }
}
  1. 位置三。在 GC 结束的时刻,再判断一下标记循环是否需要开启,满足则标记为需要开启。
void G1Policy::maybe_start_marking() {
    // 判断是否需要启动标记循环
    if (need_to_start_conc_mark("end of GC")) {
        // 满足就标记为需要启动
        collector_state()->set_initiate_conc_mark_if_possible(true);
    }
}
  1. 位置四。在 Full GC 结束的时候。判断标记循环是否需要开启,满足则标记为需要开启。
void G1Policy::record_full_collection_end() {
    // 判断是否需要启动标记循环,满足就设置为需要启动
    collector_state()->set_initiate_conc_mark_if_possible(need_to_start_conc_mark("end of Full GC", 0));
}
  1. 位置五。在元空间分配空间失败后,会直接触发一次 GC,在 GC 前会调用该方法。由于条件一定满足,所以也会设置为需要开启并发标记。
bool VM_CollectForMetadataAllocation::initiate_concurrent_GC() {
    // 第一个条件,使用G1 GC。第二个条件默认值是 true
    if (UseG1GC && ClassUnloadingWithConcurrentMark) {
        g1h->g1_policy()->collector_state()->set_initiate_conc_mark_if_possible(true);
    }
}

在这五个位置中,触发频次最高的是位置三,其次是位置二。分别定义它们为GC 结束前标记和强制标记。

GC 结束前标记

方法回顾

该方法涉及一个条件 need_to_start_conc_mark(),接下来分析一下该方法。

void G1Policy::maybe_start_marking() {
    // 判断是否需要启动标记循环
    if (need_to_start_conc_mark("end of GC")) {
        // 满足就标记为需要启动
        collector_state()->set_initiate_conc_mark_if_possible(true);
    }
}

need_to_start_conc_mark()

不严谨的总结如下:在老年代已激活的 region 中,如果这些 region 总占用空间除以总容量的百分比超过 X%,则返回 true。在默认参数的情况下,X 的值大概在 45 上下。

bool G1Policy::need_to_start_conc_mark(const char* source, size_t alloc_word_size) {
    // 此方法的解释在下面
    if (about_to_start_mixed_phase()) {
        // 不满足执行条件
        return false;
    }
    // 触发并发标记的阈值,该值是当前已激活 region 空间的 X%,X 和 G1 的参数 G1UseAdaptiveIHOP 有关
    // 在 G1UseAdaptiveIHOP == false 的情况下, 默认是 InitiatingHeapOccupancyPercent = 45 这个值
    // 在 G1UseAdaptiveIHOP == true 的情况下(默认是true), 会通过预测算法计算出一个值
    size_t marking_initiating_used_threshold = _ihop_control->get_conc_mark_start_threshold();
    // 可以视为老年代的当前占用大小,单位字节
    size_t cur_used_bytes = _g1h->non_young_capacity_bytes();
    // 本方法被调用,是因为在老年代分配了一个新对象,
    // 对象的大小是 alloc_word_size (单位word), 64位jvm的HeapWordSize=8,32位jvm的HeapWordSize=4
    // alloc_byte_size 就是新对象的字节数
    size_t alloc_byte_size = alloc_word_size * HeapWordSize;
    size_t marking_request_bytes = cur_used_bytes + alloc_byte_size;

    bool result = false;
    if (marking_request_bytes > marking_initiating_used_threshold) {
        // 当前计算出来的老年代占用空间 达到了并发标记触发的阈值
        // 只要当前不是 Mix Gc、Full Gc、下次就是Mix Gc三种状态就返回 true
        result = collector_state()->in_young_only_phase() && !collector_state()->in_young_gc_before_mixed();
    }

    return result;
}

bool G1Policy::about_to_start_mixed_phase() const {
    // 条件1:[G1 Main Marker] 这个线程当前不处于 Idle 状态,附录中提供了全部的三个状态
    // 条件2:当前处在下一次GC会执行MixGC之前的阶段
    // 满足任意条件返回 true
    return _g1h->concurrent_mark()->cm_thread()->during_cycle() || collector_state()->in_young_gc_before_mixed();
}

调用位置

  1. maybe_start_marking() 的调用链如下,可以看到有两个位置:
void G1Policy::record_collection_pause_end(double pause_time_ms, size_t cards_scanned, size_t heap_used_bytes_before_gc) {
    bool this_pause_included_initial_mark = false;
    this_pause_included_initial_mark = collector_state()->in_initial_mark_gc();
    
    if (this_pause_included_initial_mark) {
        // 该条件为真时,说明 _initiate_conc_mark_if_possible 状态已经被设置为 true。
        // 这意味着本次 GC Collection 一定会启动并发标记循环
        // 那毫无疑问,并发标记大概率不会在下一次 GC 时就结束,所以本次也就没必要判断需不需要设置启动标志了。
    } else {
        // maybe_start_marking() 的主要作用就是将 _initiate_conc_mark_if_possible 置为 true
        maybe_start_marking();
    }
    if (collector_state()->in_young_gc_before_mixed()) {
        // 说明下一次 GC 就是 Mixed GC 了,
    } else if (!this_pause_was_young_only) {
        // 这次 GC 收集不是完全 Young GC
        if (!next_gc_should_be_mixed("continue mixed GCs", "do not continue mixed GCs")) {
            // 下一次 GC 也不是 Mixed GC。那么就判断要不要设置启动标记
            maybe_start_marking();
        }
    }
}

看完这段代码,我对第二个 maybe_start_marking(); 调用有疑惑。按理说第一个调用已经满足需求了,第二个调用反而会出现重复调用的情况。g1Policy.cpp

  1. 查看 record_collection_pause_end() 的调用,下面的方法在启动标记循环的路径的步骤5中也出现过
bool G1CollectedHeap::do_collection_pause_at_safepoint(double target_pause_time_ms) {
    if (!_cm_thread->should_terminate()) {
        // 该方法在启动标记循环的路径里的步骤 4 出现过
        // 回顾一下,在调用完 decide_on_conc_mark_initiation() 之后,才会让 should_start_conc_mark 这个值变为真
        g1_policy()->decide_on_conc_mark_initiation();
    }
    // in_initial_mark_gc() 方法对应的变量可能会被上面的 decide_on_conc_mark_initiation() 方法置为 true
    // 所以 should_start_conc_mark 的值取决于 decide_on_conc_mark_initiation()
    bool should_start_conc_mark = collector_state()->in_initial_mark_gc();
    
    // 该位置调用了目标方法 record_collection_pause_end() 
    g1_policy()->record_collection_pause_end(pause_time_ms, total_cards_scanned, heap_used_bytes_before_gc);
    
    // 在上面介绍过了
    if (should_start_conc_mark) {
        // 通知标记线程启动 
        do_concurrent_mark();
    }
}
  1. 上面引入了一些和本小节无关的代码,主要是为了阐述代码之间的时间线调用顺序,整理如下:
bool G1CollectedHeap::do_collection_pause_at_safepoint(double target_pause_time_ms) {
    // 这是第 N 次的 GC 调用,在下面执行完毕后,将 initiate_conc_mark_if_possible 置为了 true
    g1_policy()->record_collection_pause_end(pause_time_ms, total_cards_scanned, heap_used_bytes_before_gc);
}
bool G1CollectedHeap::do_collection_pause_at_safepoint(double target_pause_time_ms) {
    // 这是第 N+1 次的 GC 调用
    if (!_cm_thread->should_terminate()) {
        // 在本方法中,如果 initiate_conc_mark_if_possible 为真,
        // 则将 in_initial_mark_gc 置为 true
        g1_policy()->decide_on_conc_mark_initiation();
    }
    // 如果 in_initial_mark_gc 被置为了真,则 should_start_conc_mark 为真
    bool should_start_conc_mark = collector_state()->in_initial_mark_gc();
    if (should_start_conc_mark) {
        // 如果为真,则通知标记线程启动标记循环
        do_concurrent_mark();
    }
}

强制标记

方法回顾

bool G1Policy::force_initial_mark_if_outside_cycle(GCCause::Cause gc_cause) {
    // 标记循环是否已经启动了
   bool during_cycle = _g1h->concurrent_mark()->cm_thread()->during_cycle();
   if (!during_cycle) {
      // 循环没启动的话,标记为需要启动
      collector_state()->set_initiate_conc_mark_if_possible(true);
      return true;
   }
}

调用位置

  1. 该方法会调用上面的强制标记方法,但是需要满足一个条件 _should_initiate_conc_mark 为 true
void VM_G1CollectForAllocation::doit() {
    if (_should_initiate_conc_mark) {
        bool res = g1h->g1_policy()->force_initial_mark_if_outside_cycle(_gc_cause);
    }
}
  1. 在 VM_G1CollectForAllocation 的构造方法中,对 _should_initiate_conc_mark 进行赋值
class VM_G1CollectForAllocation {
    public:
      bool _should_initiate_conc_mark;
      GCCause::Cause _gc_cause;                // the putative cause for this gc op
};
VM_G1CollectForAllocation::VM_G1CollectForAllocation(size_t         word_size,
         uint           gc_count_before,
         GCCause::Cause gc_cause,
         bool should_initiate_conc_mark,
         double target_pause_time_ms) : VM_CollectForAllocation(word_size, gc_count_before, gc_cause),
            _should_initiate_conc_mark(should_initiate_conc_mark) {
    _gc_cause = gc_cause;
}
  1. 通过查找 VM_G1CollectForAllocation 的实例化位置,且参数 should_initiate_conc_mark 为 true。查到如下方法:
void G1CollectedHeap::collect(GCCause::Cause cause) {
    if (should_do_concurrent_full_gc(cause)) {
        VM_G1CollectForAllocation op(0,     /* word_size */
                                   gc_count_before,
                                   cause,
                                   true,  /* should_initiate_conc_mark */
                                   g1_policy()->max_pause_time_ms());
        // 将 GC 收集的请求转发到 VM Thread 中,
        // 然后执行 VM_G1CollectForAllocation 类的 doit 方法
        VMThread::execute(&op);
    }
}
bool G1CollectedHeap::should_do_concurrent_full_gc(GCCause::Cause cause) {
    switch (cause) {
        // GCLockerInvokesConcurrent 默认值是 false
        case GCCause::_gc_locker:               return GCLockerInvokesConcurrent;
        // 如果是分配大对象,则为为 true
        case GCCause::_g1_humongous_allocation: return true;
        // 只要是用户主动发起的 GC 请求,则会返回 true
        default:                                return is_user_requested_concurrent_full_gc(cause);
    }
}
  1. 通过查看上面的方法,得知调用启动标记的前提是 cause 得是 _g1_humongous_allocation 或者用户主动请求的 GC。考虑到篇幅,只探索大对象分配。下面看下大对象分配触发 GC 的位置:
HeapWord* G1CollectedHeap::attempt_allocation_humongous(size_t word_size) {
    // 调用在
    if (g1_policy()->need_to_start_conc_mark("concurrent humongous allocation", word_size)) {
        collect(GCCause::_g1_humongous_allocation);
    }
}
  1. 在上面的方法中,也调用了 need_to_start_conc_mark() 方法,内容在 GC 结束前标记 处有过介绍

大对象的定义

大对象的阈值是单个region大小的一半,举例来说64位下region最小值是8Mb,所以超过4Mb就是大对象

static bool is_humongous(size_t word_size) {
    // 判断是否是一个大对象,通过下面的构造函数,可以看到_humongous_object_threshold_in_words的赋值代码
    return word_size > _humongous_object_threshold_in_words;
}
G1CollectedHeap::G1CollectedHeap(G1CollectorPolicy* collector_policy){
    // 大对象阈值的赋值,需要注意的是这里的 HeapRegion::GrainWords 单位是 word,在64位下,1 word = 8字节
    _humongous_object_threshold_in_words = humongous_threshold_for(HeapRegion::GrainWords);
}
static size_t humongous_threshold_for(size_t region_size) {
    // 结合上面的传参,可知这里 region_size 的单位是 word
    // region空间是 1Mb ~ 32 Mb 之间,64位下换算成word 是 131072 word ~ 4194304 word
    // 阈值是 region_size 的一半,也就是 
    return (region_size / 2);
}

Clone this wiki locally