Repository navigation
g1 mark Conc_Mark
ceragon edited this page Dec 20, 2021
·
5 revisions
下面是启动标记循环的唯一方法,且该方法会在 VM Thread 中执行,标记标记循环启动之后,唤醒 G1 Main Marker 线程, 之后的标记循环的流程管理由 G1 Main Marker 线程负责。
- 启动循环的直接方法
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();
}
}- 下面的方法是调用 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();
}
}- 下面的方法是唯一把 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);
}- 下面的方法是在决定这次是否是一个附带并发标记初始化的 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();
}
}
}- 接着查看 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();
}
}- 再次追踪调用链,查到两个方法
- Young Gc、Mix GC、的 GC 方法. GC Pause 触发流程
- Meta Space 分配空间失败调用GC的方法. 元空间 GC 触发流程
根据上面的步骤4,如果 _initiate_conc_mark_if_possible 不为真,则无法启动标记循环。以下是将该值赋值为真的位置列表
- 位置一。在 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;
}
}- 位置二。只要当前标记循环没有开启,就强行设置为标记循环需要启动。
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;
}
}- 位置三。在 GC 结束的时刻,再判断一下标记循环是否需要开启,满足则标记为需要开启。
void G1Policy::maybe_start_marking() {
// 判断是否需要启动标记循环
if (need_to_start_conc_mark("end of GC")) {
// 满足就标记为需要启动
collector_state()->set_initiate_conc_mark_if_possible(true);
}
}- 位置四。在 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));
}- 位置五。在元空间分配空间失败后,会直接触发一次 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 结束前标记和强制标记。
该方法涉及一个条件 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);
}
}不严谨的总结如下:在老年代已激活的 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();
}- 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
- 查看 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();
}
}- 上面引入了一些和本小节无关的代码,主要是为了阐述代码之间的时间线调用顺序,整理如下:
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;
}
}- 该方法会调用上面的强制标记方法,但是需要满足一个条件 _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);
}
}- 在 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;
}- 通过查找 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);
}
}- 通过查看上面的方法,得知调用启动标记的前提是 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);
}
}- 在上面的方法中,也调用了 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);
}