Skip to content

g1 evacuation Mixed_GC_Main

ceragon edited this page Dec 30, 2021 · 4 revisions

Mixed GC 回收过程

GC 是 Garbage Collection 的简称,在 G1 中 GC 采用的是标记复制算法.

G1 GC 过程是把存活对象从一个 region 复制到另外一个 region,然后重置原 region,这个过程就像是在转移对象。

所以下文涉及到 'GC 过程' 的中文表述时,我会写成年轻代转移和老年代转移,而不是回收,特此声明。

相关方法

bool G1CollectedHeap::do_collection_pause_at_safepoint(double target_pause_time_ms) {
    // .........
}

流程概述

  • 预测年轻代转移耗时
  • 选择老年代的转移集合
  • 处理大对象 region
  • 根扫描
  • 处理转移专用的卡表
  • 处理记忆集合
  • 执行年轻代的转移
  • 执行老年代的转移

预测年轻代转移耗时

  • 统计未处理的 card table 预计耗时
  • 统计执行年轻代 region 转移到预计耗时
相关代码
  1. 以下是预测年轻代转移耗时的相关位置
void G1Policy::finalize_collection_set(
                    double target_pause_time_ms, // 在此方法中 = MaxGCPauseMillis 
                    G1SurvivorRegions* survivor // survivor region 的列表
                                        ) {
    // time_remaining_ms = target_pause_time_ms - 转移年轻代的预测耗时。所以该值是剩余可用的暂停时间,
    double time_remaining_ms = _collection_set->finalize_young_part(target_pause_time_ms, survivor);
}
  1. 以下是主体逻辑
double G1CollectionSet::finalize_young_part(double target_pause_time_ms, G1SurvivorRegions* survivors) {
    // TODO: 
    finalize_incremental_building();
    // 待处理的卡表数量
    size_t pending_cards = _policy->pending_cards();
    // 根据卡表的数量预测处理耗时
    double base_time_ms = _policy->predict_base_elapsed_time_ms(pending_cards);
    // 还剩多少可用时间
    double time_remaining_ms = MAX2(target_pause_time_ms - base_time_ms, 0.0);
    // survivor 区的 region 个数
    uint survivor_region_length = survivors->length();
    // eden 区的 region 个数
    uint eden_region_length = _g1h->eden_regions_count();
    // 给 G1CollectionSet 类的成员变量赋值
    init_region_lengths(eden_region_length, survivor_region_length);
    // 将全部新生代 region 的索引放入到 _heap_region_indices 数组中
    verify_young_cset_indices();
    // 将全部 survivor region 标记为 eden region,并初始化 (只设置size字段,不清除数据)
    survivors->convert_to_eden();
    // 这里的 _inc_bytes_used_before 是年轻代的总空间占用
    // 记录一下 GC 前的年轻代空间占用
    _bytes_used_before = _inc_bytes_used_before;
    // _inc_predicted_elapsed_time_ms 是预测出来的转移年轻代 region 花费的时间
    time_remaining_ms = MAX2(time_remaining_ms - _inc_predicted_elapsed_time_ms, 0.0);
    // 记录一下 GC 前的年轻代 region 个数
    set_recorded_rs_lengths(_inc_recorded_rs_lengths);
    // 如果 young region 的转移执行完成之后,还剩余多少可用的时间
    return time_remaining_ms;
}

finalize_collection_set() 涉及的信息

_collection_set 的赋值

解析
  1. _collection_set 被下面的方法赋值
void G1Policy::init(G1CollectedHeap* g1h, G1CollectionSet* collection_set) {
    _collection_set = collection_set;
    // 初始化 _collection_set 
    _collection_set->start_incremental_building();
}
  1. 在下面的方法中会调用上面的 init() 方法,且下面的方法会在 Jvm 启动阶段调用
class G1CollectedHeap : public CollectedHeap {
    G1CollectionSet _collection_set;
};
jint G1CollectedHeap::initialize() {
    g1_policy()->init(this, &_collection_set);
}

选择老年代的转移集合

相关代码
  1. 执行选择老年代转移集合的相关代码
void G1Policy::finalize_collection_set(double target_pause_time_ms, G1SurvivorRegions* survivor) {
    // 就是上面介绍过的预测年轻代转移到耗时
    double time_remaining_ms = _collection_set->finalize_young_part(target_pause_time_ms, survivor);
    // 选择老年代转移集合的主体方法
    _collection_set->finalize_old_part(time_remaining_ms);
}
  1. 主体代码
void G1CollectionSet::finalize_old_part(double time_remaining_ms) {
    // 必须处于 mixed gc 中
    if (collector_state()->in_mixed_phase()) {
        // 当前 cset 元素个数的 8% 为下限
        const uint min_old_cset_length = _policy->calc_min_old_cset_length();
        // 当前 heap 内已激活 region 总数的 10% 为上限
        const uint max_old_cset_length = _policy->calc_max_old_cset_length();
        // 从 cset 中获取一个 region(实际上是从排好序的 list 中获取第一个 region)
        HeapRegion* hr = cset_chooser()->peek();
        while (hr != NULL) {
            // 每一次 mix gc 时,纳入到转移范围的老年代 region 有个数量上限
            if (old_region_length() >= max_old_cset_length) {
                break;
            }
            // 剩余待回收的空间大小
            size_t reclaimable_bytes = cset_chooser()->remaining_reclaimable_bytes();
            // 可回收率
            double reclaimable_percent = _policy->reclaimable_bytes_percent(reclaimable_bytes);
            // 可回收率的阈值
            double threshold = (double) G1HeapWastePercent;
            if (reclaimable_percent <= threshold) {
                // 低于阈值就不用继续添加 region 了
                break;
            }
            // 根据历史经验预测一下这个 region 转移需要花费的时间
            double predicted_time_ms = predict_region_elapsed_time_ms(hr);
            // 只要年轻代的空间是自适应的,这里就是 true
            if (check_time_remaining) {
                // 如果把当前的 region 加到转移集合中,会导致超出暂停时间,则为真
                if (predicted_time_ms > time_remaining_ms) {
                    // 如果当前到转移集合还未满足最小数量,则仍然会把这个超出暂停时间的 region 加到转移集合中
                    if (old_region_length() >= min_old_cset_length) {
                        break;
                    }
                }
            } else {
                // 检查转移集合的个数是否满足最小要求
                if (old_region_length() >= min_old_cset_length) {
                    break;
                }
            }
            // 从 cset 中移除当前 region
            cset_chooser()->pop();
            // 提前设置一下这个 region 需要在转移后删除
            _g1h->old_set_remove(hr);
            // 填到转移集合中
            add_old_region(hr);
            // 取出下一个 region
            hr = cset_chooser()->peek();
        }
    }
}

finalize_old_part() 方法相关信息

calc_min_old_cset_length() 方法

解析
uint G1Policy::calc_min_old_cset_length() const {
    // cset 内的 region 个数
    const size_t region_num = (size_t) cset_chooser()->length();
    // 预期的 Mixed GC 执行次数,G1MixedGCCountTarget 默认值是 8
    const size_t gc_num = (size_t) MAX2(G1MixedGCCountTarget, (uintx) 1);
    // 每次 GC 需要处理几个 region
    size_t result = region_num / gc_num;
    if (result * gc_num < region_num) {
        // 向上取整
        result += 1;
    }
    return (uint) result;
}

calc_max_old_cset_length() 方法

解析
uint G1Policy::calc_max_old_cset_length() const {
    // g1h 是 g1 的堆空间对象
    const G1CollectedHeap *g1h = G1CollectedHeap::heap();
    // 堆中已激活的 region 个数
    const size_t region_num = g1h->num_regions();
    // G1OldCSetRegionThresholdPercent 一个阈值,单位是百分比,默认是 10%
    const size_t perc = (size_t) G1OldCSetRegionThresholdPercent;
    // 计算堆内总的 region 个数的 10% 是多少。包括老年代和新生代 region
    size_t result = region_num * perc / 100;
    if (100 * result < region_num * perc) {
        // 向上取整
        result += 1;
    }
    return (uint) result;
}

处理大对象 region

相关代码
bool G1CollectedHeap::do_collection_pause_at_safepoint(double target_pause_time_ms) {
    // 代码行数 2929
    register_humongous_regions_with_cset();
}
void G1CollectedHeap::register_humongous_regions_with_cset() {
    if (!G1EagerReclaimHumongousObjects) {
        return;
    }
    RegisterHumongousWithInCSetFastTestClosure cl;
    // 尝试把一个大对象 region 放入到一个集合中
    heap_region_iterate(&cl);
    // 是否有回收候选 region
    _has_humongous_reclaim_candidates = cl.candidate_humongous() > 0;
    // 刷到全局的脏卡队列中
    cl.flush_rem_set_entries();
}

register_humongous_regions_with_cset() 相关信息

RegisterHumongousWithInCSetFastTestClosure 闭包

class RegisterHumongousWithInCSetFastTestClosure : public HeapRegionClosure {
    virtual bool do_heap_region(HeapRegion *r) {
        // region 必须是大对象
        if (!r->is_starts_humongous()) {
            return false;
        }
        // 堆对象
        G1CollectedHeap *g1h = G1CollectedHeap::heap();
        // 是否是一个回收候选的 region
        bool is_candidate = humongous_region_is_candidate(g1h, r);
        // 这个 region 在 heap 中的索引
        uint rindex = r->hrm_index();
        // 在 heap 中标记这个 region 是一个回收候选 region
        g1h->set_humongous_reclaim_candidate(rindex, is_candidate);
        if (is_candidate) {
            // 候选次数 +1
            _candidate_humongous++;
            // 把当前 region 放入一个集合,这个集合用于快速测试是否有引用指向了集合内元素
            g1h->register_humongous_region_with_cset(rindex);
            if (!r->rem_set()->is_empty()) {
                // 转移用的记忆集不空
                // 以下是 heap 管理的卡表
                G1CardTable *ct = g1h->card_table();
                // 迭代器,遍历的是当前 region 的 remSet 中记录的 other region 里的卡表
                HeapRegionRemSetIterator hrrs(r->rem_set());
                size_t card_index;
                // 遍历卡表里的每个元素
                while (hrrs.has_next(card_index)) {
                    // card_ptr 是卡表指定元素的指针,指向索引为 card_index 的这个元素 
                    jbyte *card_ptr = (jbyte *) ct->byte_for_index(card_index);
                    // card_ptr 指向的位置必须在已使用空间范围内
                    if (g1h->is_in_closed_subset(ct->addr_for(card_ptr))) {
                        // 卡表元素的值没有标脏
                        if (*card_ptr != G1CardTable::dirty_card_val()) {
                            // 进行标脏
                            *card_ptr = G1CardTable::dirty_card_val();
                            // 入队到脏卡表队列中
                            _dcq.enqueue(card_ptr);
                        }
                    }
                }
                // 清理锁信息
                r->rem_set()->clear_locked(true /* only_cardset */);
                // 标记卡表处理完成
                r->rem_set()->set_state_complete();
            }
        }
        // 统计大 region 的个数
        _total_humongous++;
        return false;
    }
};

RegisterHumongousWithInCSetFastTestClosure 相关信息

humongous_region_is_candidate() 方法

class RegisterHumongousWithInCSetFastTestClosure : public HeapRegionClosure {
    bool humongous_region_is_candidate(G1CollectedHeap *g1h, HeapRegion *region) const {
        // 大对象 region 里面只有一个对象,所以 region 的底部地址就是对象的首地址
        oop obj = oop(region->bottom());
        // 对象已经死亡就不管了
        if (g1h->is_obj_dead(obj, region)) {
            return false;
        }
        // remember set 是否已经更新完成,没有更新完成会返回 false
        if (!region->rem_set()->is_complete()) {
            return false;
        }
        // 对象是数组,且这个 region 是否是一个潜在的急切需要回收的候选 region
        return obj->is_typeArray() &&
               g1h->is_potential_eager_reclaim_candidate(region);
    }
};

根扫描

相关代码
bool G1CollectedHeap::do_collection_pause_at_safepoint(double target_pause_time_ms) {
    // 代码行数 2952
    evacuate_collection_set(&per_thread_states);
}
void G1CollectedHeap::evacuate_collection_set(G1ParScanThreadStateSet *per_thread_states) {
    // 已激活 worker 线程数量
    const uint n_workers = workers()->active_workers();
    // 根遍历的方法
    G1RootProcessor root_processor(this, n_workers);
    // 封装一个并行执行任务
    G1ParTask g1_par_task(this, per_thread_states, _task_queues, &root_processor, n_workers);
    // 开始同步执行
    workers()->run_task(&g1_par_task);
}

evacuate_collection_set() 相关信息

G1RootProcessor 与 G1ParTask

相关代码
class G1ParTask : public AbstractGangTask {
protected:
    G1RootProcessor* _root_processor; // 通过构造函数传入
    void work(uint worker_id) {
        _root_processor->evacuate_roots(pss, worker_id);
    }
};
void G1RootProcessor::evacuate_roots(G1ParScanThreadState* pss, uint worker_i) {
    G1EvacuationRootClosures *closures = pss->closures();
    // 遍历java 线程的根,和 VMThrea的 线程的根
    process_java_roots(closures, phase_times, worker_i);
    // 结合上下文,这里是 false
    if (closures->trace_metadata()) {
        worker_has_discovered_all_strong_classes();
    }
    // 看不懂的一些根
    process_vm_roots(closures, phase_times, worker_i);
    // 字符串常量池的根
    process_string_table_roots(closures, phase_times, worker_i);
    if (!_process_strong_tasks.is_task_claimed(G1RP_PS_refProcessor_oops_do)) {
        // 将并发标记的存活列表视为根
        _g1h->ref_processor_cm()->weak_oops_do(closures->strong_oops());
    }
    if (closures->trace_metadata()) {
        {
            // 等待强引用检索完毕
            wait_until_all_strong_classes_discovered();
        }
        // 类加载器的根
        ClassLoaderDataGraph::roots_cld_do(NULL, closures->second_pass_weak_clds());
    }
    if (!_process_strong_tasks.is_task_claimed(G1RP_PS_filter_satb_buffers) &&
        _g1h->collector_state()->mark_or_rebuild_in_progress()) {
        // 并发标记期间做一些处理
        G1BarrierSet::satb_mark_queue_set().filter_thread_buffers();
    }
    // 本线程处理完毕,并修改完成状态
    _process_strong_tasks.all_tasks_completed(n_workers());
}

Clone this wiki locally