G1 Full GC

君子以自强不息。

什么时候触发 Full GC

当对象分配失败,会进入到 Evac 失败过程,处理失败, 再次尝试分配,仍不成功,进行 Full GC。

Evac 失败

Evac 失败栈的处理过程是串行的。 处理过程发生在 G1ParCopyColsure 中, 就是把对象加入到 dirty card 队列中处理。

如果对象复制发生了一部分,将会直接更新对象的 RSet,不需要对已经复制的对象做额外回收之类的处理。

对象复制如果失败,则把对象的指针指向自己。所以整个 JVM 中如果发现指针指向自己则认为发生了复制失败。所以在处理 Evac 失败的时候,需要检查是否有指向自己的指针。如果有的话,则需要删除指针,恢复对象头。

串行 Full GC

Evac 失败后会进入到 FGC,在 JDK 10 之前 FGC 都是串行回收。串行 Full GC 流程:

标记活跃对象

处理代码对象:

1
2
3
4
5
6
7
MarkingCodeBlobClosure follow_code_closure ( &GenMarkSweep::follow_root_closure, !CodeBlobToOopClosure::FixRelocations);
{
    G1RootProcessor root_processor (g1h);
    root_processor.process_strong_roots( &GenMarkSweep::follow_root_closure,
                            &GenMarkSweep::follow_cld_closure,
                            &follow_code_closure);
}

针对所有的根处理,通过 FollowRootClosure 触发标记:

1
2
3
4
5
6
7
8
9
10
11
template <class T> inline void MarkSweep::follow_root(T* p) {
    T heap_oop = oopDesc::load_heap_oop(p);
    if (!oopDesc::is_null(heap_oop)) {
        oop obj = oopDesc::decode_heap_oop_not_null(heap_oop);
        if (!obj->mark()->is_marked()) {
            mark_object(obj);
            obj->follow_contents();
        }
    }
    follow_stack();
}

对栈的对象一个一个遍历处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void MarkSweep::follow_stack() {
    do {
        while (!_marking_stack.is_empty()) {
            oop obj = _marking_stack.pop();
            assert (obj->is_gc_marked(), "p must be marked");
            obj->follow_contents();
        }
        // 在处理对象数组时,需要一个元素一个元素地处理,如果直接处理整个数组对象可能
        // 导致标记溢出
        if (!_objarray_stack.is_empty()) {
            ObjArrayTask task = _objarray_stack.pop();
            ObjArrayKlass* k = (ObjArrayKlass*)task.obj()->klass();
            k->oop_follow_contents(task.obj(), task.index());
        }
    } while (!_marking_stack.is_empty() || !_objarray_stack.is_empty());
}

对引用对象标记处理:

1
2
3
4
5
ReferenceProcessor* rp = GenMarkSweep::ref_processor();
rp->setup_policy(clear_all_softrefs);
const ReferenceProcessorStats& stats =
        rp->process_discovered_references(&GenMarkSweep::is_alive, &GenMarkSweep::keep_alive,
            &GenMarkSweep::follow_stack_closure, NULL, gc_timer(), gc_tracer()->gc_id());

计算对象的新地址

从分区的底部开始扫描,同时设置compact top也为底部,当对象被标记,即活跃时,把对象的oop指针设置为compact top,这个值就是对象应该所处的位置。

更新引用对象的地址

把活跃对象和活跃对象中的引用更新到新位置。

移动对象完成压缩

最后一步就是完成空间的压缩。遍历时必须从前向后依次开始,否则数据会被破坏。