part 3
DefNew的GC属于Minor GC,使用copying算法进行垃圾收集,是Serial GC(-XX:+UseSerialGC)的新生代部分,接下来分析一下Serial GC的老年代部分,也就是Serial Old;TenuredGeneration是Serial Old的堆实现,这里还是要说一下什么情况下可能会发生Old GC,在分析DefNew的时候提到了所谓的"空间分配担保",也就是YoungGen在即将进行Minor GC的时候,让OldGen判断一下是否可以进行这次Minor GC,判断的方法是OldGen可用的连续空间大于新生代的对象大小或者大于新生代历史晋升的平均大小,如果这个条件成立的话,那么Minor GC就会进行,否则就会进行一次Major GC;下面将以TenuredGeneration的实现来分析一下OldGC的实现细节。
TenuredGeneration使用标记-整理算法进行垃圾收集,包括标记、压缩、清理三个核心步骤,TenuredGeneration::collect是TenuredGeneration垃圾收集工作的入口:
void TenuredGeneration::collect(bool full,
bool clear_all_soft_refs,
size_t size,
bool is_tlab) {
GenCollectedHeap* gch = GenCollectedHeap::heap();
// Temporarily expand the span of our ref processor, so
// refs discovery is over the entire heap, not just this generation
ReferenceProcessorSpanMutator
x(ref_processor(), gch->reserved_region());
STWGCTimer* gc_timer = GenMarkSweep::gc_timer();
gc_timer->register_gc_start();
SerialOldTracer* gc_tracer = GenMarkSweep::gc_tracer();
gc_tracer->report_gc_start(gch->gc_cause(), gc_timer->gc_start());
gch->pre_full_gc_dump(gc_timer);
GenMarkSweep::invoke_at_safepoint(ref_processor(), clear_all_soft_refs);
gch->post_full_gc_dump(gc_timer);
gc_timer->register_gc_end();
gc_tracer->report_gc_end(gc_timer->gc_end(), gc_timer->time_partitions());
}
主要关注GenMarkSweep::invoke_at_safepoint函数调用,这是整个TennredGeneration垃圾收集的核心,invoke_at_safepoint函数通过调用下面四个函数来做具体的垃圾收集工作。
// Mark live objects
static void mark_sweep_phase1(bool clear_all_softrefs);
// Calculate new addresses
static void mark_sweep_phase2();
// Update pointers
static void mark_sweep_phase3();
// Move objects to new positions
static void mark_sweep_phase4();
下面根据每个步骤分别来分析一下具体的GC过程。
- (1)、首先是标记阶段:mark_sweep_phase1
来看看full_process_roots函数的具体情况:
void GenCollectedHeap::full_process_roots(StrongRootsScope* scope,
bool is_adjust_phase,
ScanningOption so,
bool only_strong_roots,
OopsInGenClosure* root_closure,
CLDClosure* cld_closure) {
MarkingCodeBlobClosure mark_code_closure(root_closure, is_adjust_phase);
OopsInGenClosure* weak_roots = only_strong_roots ? NULL : root_closure;
CLDClosure* weak_cld_closure = only_strong_roots ? NULL : cld_closure;
process_roots(scope, so, root_closure, weak_roots, cld_closure, weak_cld_closure, &mark_code_closure);
if (is_adjust_phase) {
// We never treat the string table as roots during marking
// for the full gc, so we only need to process it during
// the adjust phase.
process_string_table_roots(scope, root_closure);
}
_process_strong_tasks->all_tasks_completed(scope->n_threads());
}
process_roots是需要重点关注的函数,这个函数将扫描出所有可以作为GCRoot的对象,扫描的地方非常多,可以参考下面这个代码片段:
这个strong_roots就是上面提到的follow_root_closure,他负责标记存活的对象,去它对应的do_oop函数看看到底是怎么做的:
void MarkSweep::FollowRootClosure::do_oop(oop* p) {
follow_root(p);
}
template <class T> inline void MarkSweep::follow_root(T* p) {
assert(!Universe::heap()->is_in_reserved(p),
"roots shouldn't be things within the heap");
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() &&
!is_archive_object(obj)) {
mark_object(obj);
follow_object(obj);
}
}
follow_stack();
}
对象是否被标记过时存储在对象头里面的,如果一个对象没有被标记过,就会用mark_object将会标记一个对象,具体看看mark_object的实现:
inline void MarkSweep::mark_object(oop obj) {
#if INCLUDE_ALL_GCS
if (G1StringDedup::is_enabled()) {
// We must enqueue the object before it is marked
// as we otherwise can't read the object's age.
G1StringDedup::enqueue_from_mark(obj);
}
#endif
// some marks may contain information we need to preserve so we store them away
// and overwrite the mark. We'll restore it at the end of markSweep.
markOop mark = obj->mark();
obj->set_mark(markOopDesc::prototype()->set_marked());
if (mark->must_be_preserved(obj)) {
preserve_mark(obj, mark);
}
}
调用了oop的set_mark方法进行对象标记,如果对象头里面的信息需要被保存起来稍后GC完成需要恢复,那么就要调用preserve_mark将对象头的信息存储起来,mark是对象的oldMark,厦门市hipreserve_mark的实现:
// We preserve the mark which should be replaced at the end and the location
// that it will go. Note that the object that this markOop belongs to isn't
// currently at that address but it will be after phase4
void MarkSweep::preserve_mark(oop obj, markOop mark) {
// We try to store preserved marks in the to space of the new generation since
// this is storage which should be available. Most of the time this should be
// sufficient space for the marks we need to preserve but if it isn't we fall
// back to using Stacks to keep track of the overflow.
if (_preserved_count < _preserved_count_max) {
_preserved_marks[_preserved_count++].init(obj, mark);
} else {
_preserved_mark_stack.push(mark);
_preserved_oop_stack.push(obj);
}
}
这个函数较为简单,如果_preserved_marks里面存储了太多的对象头信息超出限制了,那么就将对象头信息分别存储在_preserved_mark_stack和_preserved_oop_stack两个栈里面,否则存储在_preserved_marks里面去;说完了对象的标记,下面来看看follow_object;
inline void MarkSweep::follow_object(oop obj) {
assert(obj->is_gc_marked(), "should be marked");
if (obj->is_objArray()) {
// Handle object arrays explicitly to allow them to
// be split into chunks if needed.
MarkSweep::follow_array((objArrayOop)obj);
} else {
obj->oop_iterate(&mark_and_push_closure);
}
}
follow_object根据名字可以猜测是处理obj的引用,我想这也是一个递归的过程,具体看看上面的代码片段,如果对象是一个数组对象,那么就使用follow_array来处理,否则使用对象的oop_iterate函数来处理,数组对象单独处理的原因是如果数组对象和普通对象一起处理,数组对象非常大的时候可能会影响普通对象的处理;follow_array最后依然还是使用follow_object来处理数组元素中的对象的,看看follow_array:
inline void MarkSweep::follow_array(objArrayOop array) {
MarkSweep::follow_klass(array->klass());
// Don't push empty arrays to avoid unnecessary work.
if (array->length() > 0) {
MarkSweep::push_objarray(array, 0);
}
}
如果数组长度大于0,那么就使用push_objarray来处理这个数组:
void MarkSweep::push_objarray(oop obj, size_t index) {
ObjArrayTask task(obj, index);
assert(task.is_valid(), "bad ObjArrayTask");
_objarray_stack.push(task);
}
push_objarray将数组push到了_objarray_stack栈里面,follow_stack函数会去处理_objarray_stack栈中的数组对象:
void MarkSweep::follow_stack() {
do {
while (!_marking_stack.is_empty()) {
oop obj = _marking_stack.pop();
assert (obj->is_gc_marked(), "p must be marked");
follow_object(obj);
}
// Process ObjArrays one at a time to avoid marking stack bloat.
if (!_objarray_stack.is_empty()) {
ObjArrayTask task = _objarray_stack.pop();
follow_array_chunk(objArrayOop(task.obj()), task.index());
}
} while (!_marking_stack.is_empty() || !_objarray_stack.is_empty());
}
从_marking_stack中拿出数组对象之后,调用follow_object继续处理,但是这时候follow_object里面的已经不是一个纯粹的数组对象了,已经是一个ObjArrayTask对象了,具体的标记过程泰国复杂就不继续深入了。
对象标记的工作完成之后,就要开始处理发现的对象了:
这个处理过程和Minor GC时的处理是一样的;接下来会做一些清理工作:
SystemDictionary::do_unloading用于卸载一些不再使用到的类;CodeCache::do_unloading用于卸载一些不再使用到的方法(编译好的方法会放在CodeCache里面去);Klass::clean_weak_klass_links用于清理weak reference;StringTable::unlink(&is_alive)用于删除一些不再使用的字符串常量;SymbolTable::unlink()用于从符号表中清理那些不再使用的符号;
- (2)、接着是mark_sweep_phase2,重新计算存活对象的新地址
首先看prepare_for_compaction这个函数:
void GenCollectedHeap::prepare_for_compaction() {
// Start by compacting into same gen.
CompactPoint cp(_old_gen);
_old_gen->prepare_for_compaction(&cp);
_young_gen->prepare_for_compaction(&cp);
}
首先看oldGen的prepare_for_compaction函数实现:
void Generation::prepare_for_compaction(CompactPoint* cp) {
// Generic implementation, can be specialized
CompactibleSpace* space = first_compaction_space();
while (space != NULL) {
space->prepare_for_compaction(cp);
space = space->next_compaction_space();
}
}
这是一个循环处理过程,通过prepare_for_compaction函数来处理:
void ContiguousSpace::prepare_for_compaction(CompactPoint* cp) {
scan_and_forward(this, cp);
}
scan_and_forward这个函数名字非常直观,扫描并且做forward,forward可以理解为将对象转移到一个新的位置,整个步骤(2)只是计算出一个对象的新地址,并没有将对象转移到新的地址去,转移对象到新地址的工作将在接下来的步骤(3)里面进行,下面的代码片段是步骤(2)处理的核心:
while (cur_obj < scan_limit) {
assert(!space->scanned_block_is_obj(cur_obj) ||
oop(cur_obj)->mark()->is_marked() || oop(cur_obj)->mark()->is_unlocked() ||
oop(cur_obj)->mark()->has_bias_pattern(),
"these are the only valid states during a mark sweep");
if (space->scanned_block_is_obj(cur_obj) && oop(cur_obj)->is_gc_marked()) {
// prefetch beyond cur_obj
Prefetch::write(cur_obj, interval);
size_t size = space->scanned_block_size(cur_obj);
compact_top = cp->space->forward(oop(cur_obj), size, cp, compact_top);
cur_obj += size;
end_of_live = cur_obj;
} else {
// run over all the contiguous dead objects
HeapWord* end = cur_obj;
do {
// prefetch beyond end
Prefetch::write(end, interval);
end += space->scanned_block_size(end);
} while (end < scan_limit && (!space->scanned_block_is_obj(end) || !oop(end)->is_gc_marked()));
// see if we might want to pretend this object is alive so that
// we don't have to compact quite as often.
if (cur_obj == compact_top && dead_spacer.insert_deadspace(cur_obj, end)) {
oop obj = oop(cur_obj);
compact_top = cp->space->forward(obj, obj->size(), cp, compact_top);
end_of_live = end;
} else {
// otherwise, it really is a free region.
// cur_obj is a pointer to a dead object. Use this dead memory to store a pointer to the next live object.
*(HeapWord**)cur_obj = end;
// see if this is the first dead region.
if (first_dead == NULL) {
first_dead = cur_obj;
}
}
// move on to the next object
cur_obj = end;
}
}
这个代码较长,主要完成的就一件事情,就是找到那些存活的对象,然后给这些存活的对象计算一个新的地址;为对象计算新地址的工作由CompactibleSpace::forward完成:
CompactibleSpace::forward首先试图找到一块合适的内存来存放存活的对象,然后判断这块内存是否和存活对象目前所在的位置一样,如果一样的话就没必要移动了,否则就要改变指针来移动对象,移动的工作将在(3)中进行。
上面说了对存活对象的处理,对于死亡对象,首先找到下一个存活的对象,也就是找到一段连续的死亡对象,然后判断是否可以将这段死亡对象也当成是"活的"对象,判断条件还是比较严格的,首先,这段死亡对象的起点应该是compact_top,也就是空闲的空间起点(对于forward来说),并且通过dead_spacer.insert_deadspace的校验:
bool insert_deadspace(HeapWord* dead_start, HeapWord* dead_end) {
if (!_active) {
return false;
}
size_t dead_length = pointer_delta(dead_end, dead_start);
if (_allowed_deadspace_words >= dead_length) {
_allowed_deadspace_words -= dead_length;
CollectedHeap::fill_with_object(dead_start, dead_length);
oop obj = oop(dead_start);
obj->set_mark(obj->mark()->set_marked());
assert(dead_length == (size_t)obj->size(), "bad filler object size");
log_develop_trace(gc, compaction)("Inserting object to dead space: " PTR_FORMAT ", " PTR_FORMAT ", " SIZE_FORMAT "b",
p2i(dead_start), p2i(dead_end), dead_length * HeapWordSize);
return true;
} else {
_active = false;
return false;
}
}
};
_allowed_deadspace_words是允许死亡对象存储的空间大小,这部分空间是属于浪费调的,如果太大那就不行了,那为什么还要将死亡对象也当成"活着"的对象对待呢?因为对象拷贝也是有损耗的,如果一段死亡对象刚好不需要移动,并且浪费掉的空间在可以接受的范围内,那么何乐而不为呢?insert_deadspace这个方法就是做这件事情的,当然,这段死亡的对象会被使用一个新的长度和原来这段死亡对象长度相等的一个对象替换。
- (3)、步骤(2)完成了存活对象的新地址计算,那么步骤(3)的主要工作就是将对象转移到新的地址去,也算是"整理"了;
转移对象到新地址的工作由AdjustPointerClosure来完成,直接来看这个Closure的do_oop方法吧;
void MarkSweep::AdjustPointerClosure::do_oop(oop* p) { do_oop_nv(p); }
void MarkSweep::AdjustPointerClosure::do_oop_nv(T* p) { adjust_pointer(p); }
接着看adjust_pointer这个函数;
template <class T> inline void MarkSweep::adjust_pointer(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);
assert(Universe::heap()->is_in(obj), "should be in heap");
oop new_obj = oop(obj->mark()->decode_pointer());
assert(is_archive_object(obj) || // no forwarding of archive objects
new_obj != NULL || // is forwarding ptr?
obj->mark() == markOopDesc::prototype() || // not gc marked?
(UseBiasedLocking && obj->mark()->has_bias_pattern()),
// not gc marked?
"should be forwarded");
if (new_obj != NULL) {
if (!is_archive_object(obj)) {
assert(Universe::heap()->is_in_reserved(new_obj),
"should be in object space");
oopDesc::encode_store_heap_oop_not_null(p, new_obj);
}
}
}
}
adjust_pointer这个函数的目的是将对象p转移到new_obj里面去,在实现上,就是将new_obj的地址赋值给p即可:
static inline void encode_store_heap_oop_not_null(oop* p, oop v) {
*p = v;
}
- (4)、将所有存活的对象转移到新的地址里面去
GenCompactClosure会遍历老年代和新生代,做内存整理的工作;generation_iterate会根据配置从老年代或者新生代开始进行压缩工作:
void GenCollectedHeap::generation_iterate(GenClosure* cl,
bool old_to_young) {
if (old_to_young) {
cl->do_generation(_old_gen);
cl->do_generation(_young_gen);
} else {
cl->do_generation(_young_gen);
cl->do_generation(_old_gen);
}
}
下面是GenCompactClosure的do_generation函数:
void do_generation(Generation* gen) {
gen->compact();
}
void Generation::compact() {
CompactibleSpace* sp = first_compaction_space();
while (sp != NULL) {
sp->compact();
sp = sp->next_compaction_space();
}
}
跟着CompactibleSpace的compact函数看,CompactibleSpace::scan_and_compact是具体实现压缩工作的函数,下面来分析一下这个方法的实现细节;
(1)、如果这块内存内部没有存活的对象,那么可以直接忽略这块内容
(2)、找到第一个被GC标记过的对象,然后开始存活进行对象转移操作,对于遍历过程中遇到的没有被标记过的对象,skip;所谓转移就是通过内存对象拷贝将对象拷贝到计算好的地址去
(3)、最后需要判断是否这块内存已经空了,如果已经没有存活的对象了,那么清空整个region