Skip to content

g1 mark Conc_Mark_Cycle

ceragon edited this page Dec 20, 2021 · 3 revisions

并发标记的流程

初始标记

标记 nextTAMS

  1. 标记的位置如下,该方法处理的是单个 region
inline void HeapRegion::note_start_of_marking() {
    _next_marked_bytes = 0;
    // 将 nextTAMS 设为当前的 top()
    _next_top_at_mark_start = top();
}
  1. 调用位置如下,本方法是 NoteStartOfMarkHRClosure 类的一个方法,所以查看调用关系需要查询类的实例化位置
class NoteStartOfMarkHRClosure : public HeapRegionClosure {
    bool do_heap_region(HeapRegion* r) {
        // 执行 r 这个 region 的标记 nextTAMS 方法
        r->note_start_of_marking();
        return false;
    }
};
  1. 在预标记的方法里调用了 nextTAMS 的初始化
void G1ConcurrentMark::pre_initial_mark() {
    NoteStartOfMarkHRClosure startcl;
    _g1h->heap_region_iterate(&startcl);
}
  1. 继续查看预标记方法的调用,该方法的相关解析可以查看并发标记的启动。现在可以得到一个信息,初始标记的逻辑在 GC 收集过程中进行
bool G1CollectedHeap::do_collection_pause_at_safepoint(double target_pause_time_ms) {
    if (collector_state()->in_initial_mark_gc()) {
        concurrent_mark()->pre_initial_mark();
    }
}

根标记

  1. 在 nextBitMap 中标记一个对象的方法如下
inline bool G1ConcurrentMark::mark_in_next_bitmap(uint const worker_id, HeapRegion* const hr, oop const obj, size_t const obj_size) {
    // 如果 obj 这个对象的地址在 nextTAMS 之后,则返回真
    if (hr->obj_allocated_since_next_marking(obj)) {
        // 对象在 nextTAMS 之后分配的话,不参与标记
        return false;
    }
    HeapWord* const obj_addr = (HeapWord*)obj;
    // 此步骤是标记存活的方法。利用了 cardTable 技术
    bool success = _next_mark_bitmap->par_mark(obj_addr);
    if (success) {
        // 用于统计本 region 中存活对象的空间占用
        add_to_liveness(worker_id, obj, obj_size == 0 ? obj->size() : obj_size);
    }
    return success;
}
  1. 调用标记方法的重载方法如下
inline bool G1ConcurrentMark::mark_in_next_bitmap(uint const worker_id, oop const obj, size_t const obj_size) {
    // 获取对象所在的 region 头地址
    HeapRegion* const hr = _g1h->heap_region_containing(obj);
    return mark_in_next_bitmap(worker_id, hr, obj, obj_size);
}

根扫描-堆空间

由于 G1 的并发标记针对的是老年代,所以堆空间的根就是年轻代的 region

  1. 根扫描的过程中,会将扫描到的对象中的引用对象标记存活(或者说标为黑色对象)
inline void G1RootRegionScanClosure::do_oop_work(T* p) {
    // p 是年轻代对象的引用类型变量的指针,指向了一个老年代对象的首地址,这里其实就是创建一个对象的代理类
    T heap_oop = RawAccess<MO_VOLATILE>::oop_load(p);
    // 判断一下对象是否存在
    if (CompressedOops::is_null(heap_oop)) {
        return;
    }
    // 这里主要是处理压缩指针的情况,如果启用了压缩指针,则 heap_oop 是压缩后的对象地址
    // 需要转化为真实地址
    oop obj = CompressedOops::decode_not_null(heap_oop);
    // 标记存活方法
    _cm->mark_in_next_bitmap(_worker_id, obj);
}
  1. 下面方法会遍历单个 region 中的对象,并对对象的引用类型字段指向的老年代对象进行标记
void G1ConcurrentMark::scan_root_region(HeapRegion* hr, uint worker_id) {
    G1RootRegionScanClosure cl(_g1h, this, worker_id);
    // X86 平台下,默认值是 576
    const uintx interval = PrefetchScanIntervalInBytes;
    // region 的首地址
    HeapWord* curr = hr->bottom();
    // region 的顶部 (顶部以上的都是未使用空间)
    const HeapWord* end = hr->top();
    while (curr < end) {
        // 此方法会调用汇编指令 prefetcht0,作用是将以 curr 为起始的 interval 字节的内容强制放入 CPU 的缓存中,加快后续对于该内存区域的读取
        Prefetch::read(curr, interval);
        // 将 curr 指针包装成一个 jvm 操作 java 对象用用的对象
        oop obj = oop(curr);
        // 首先,返回的 size 就是这个对象对应的类信息中记录的对象大小(浅堆大小)
        // 其次遍历对象中的引用类型字段,取出指针值,并调用 G1RootRegionScanClosure 类的 do_oop_work 方法,也就是步骤1的方法
        int size = obj->oop_iterate_size(&cl);
        // 指针偏移到下一个对象的首地址
        curr += size;
    }
}

关于 oop_iterate_size() 的深入解读暂时没有,因为调用链太过复杂

  1. 下面的方法会遍历下一个 survivor region,并执行上面的扫描单个 region 的方法
class G1CMRootRegionScanTask : public AbstractGangTask {
    void work(uint worker_id) {
        // 取到所有的 region 集合
        G1CMRootRegions* root_regions = _cm->root_regions();
        // 竞争地获取下一个 region,详细解读在下面
        HeapRegion* hr = root_regions->claim_next();
        while (hr != NULL) {
            // 调用上面的扫描方法
            _cm->scan_root_region(hr, worker_id);
            // 再竞争地获取下一个未扫描 region 
            hr = root_regions->claim_next();
        }
    }
};
HeapRegion* G1CMRootRegions::claim_next() {
    // 取到所有年轻代 survivor region 的列表 
    const GrowableArray<HeapRegion*>* survivor_regions = _survivors->regions();
    // 用通过自旋的方式将 _claimed_survivor_index +1,并返回索引
    int claimed_index = Atomic::add(1, &_claimed_survivor_index) - 1;
    if (claimed_index < survivor_regions->length()) {
        // 索引有效,并返回这个 region 的首地址
        return survivor_regions->at(claimed_index);
    }
    return NULL;
}
  1. 下面的方法用到了并行任务线程池 G1 Conc,主要目的是将扫描所有 survivor region 的任务分到多个线程中并行执行。
class G1ConcurrentMark : public CHeapObj<mtGC> {
    // 对应的是 G1 Conc 这个线程组
    WorkGang* _concurrent_workers;
};
void G1ConcurrentMark::scan_root_regions() {
    // 只要 survivor region 个数不为 0,这里就是真
    if (root_regions()->scan_in_progress()) {
        // 构造扫描的 task
        G1CMRootRegionScanTask task(this);
        // 在 G1 Conc 线程组启动并行任务,直到所有的任务执行结束后才返回
        _concurrent_workers->run_task(&task, _num_concurrent_workers);
        // 会等待所有的并行任务处理完毕,然后执行下面的方法
        root_regions()->scan_finished();
    }
}
  1. 下面的方法会调用上面的根扫描方法,而且这是在 G1 Main Marker 线程的主循环中执行。
void G1ConcurrentMarkThread::run_service() {
    while (!should_terminate()) {
        G1ConcPhase p(G1ConcurrentPhase::SCAN_ROOT_REGIONS, this);
        _cm->scan_root_regions();
    }
}

跟扫描-非堆空间

非堆空间指的是线程的栈引用和元空间的常量池引用

void G1CollectedHeap::make_pending_list_reachable() {
    if (collector_state()->in_initial_mark_gc()) {
        oop pll_head = Universe::reference_pending_list();
        if (pll_head != NULL) {
            // Any valid worker id is fine here as we are in the VM thread and single-threaded.
            _cm->mark_in_next_bitmap(0 /* worker_id */, pll_head);
        }
    }
}

Clone this wiki locally