接Copy GC(4)继续,我们看一下push_contents的实现:
inline void oopDesc::push_contents(PSPromotionManager* pm) {
// 每一个Java Class在JVM内部都会对应一个Klass结构。每一个Klass中都记录
// 了每个类有多少具体的域,这样我们就能通过这个Klass来计算每个实例的大小
// 以及遍历这个对象所引用的其他对象。
Klass* k = klass();
if (!k->oop_is_typeArray()) {
// It might contain oops beyond the header, so take the virtual call.
k->oop_push_contents(pm, this);
}
// Else skip it. The TypeArrayKlass in the header never needs scavenging.
}
我在注释里已经写得比较清楚了。大家可以看一下,这个函数比较简单,就是调用了Klass的oop_push_contents而已。
继续追下去:
void InstanceKlass::oop_push_contents(PSPromotionManager* pm, oop obj) {
InstanceKlass_OOP_MAP_REVERSE_ITERATE(
obj,
if (PSScavenge::should_scavenge§) {
pm->claim_or_forward_depth§;
},
assert_nothing )
}
又是一个宏定义。我帮大家展开一下:
void InstanceKlass::oop_push_contents(PSPromotionManager* pm, oop obj) {
// Hotspot使用OopMapBlock这个数据结构记录了一个Klass中有多少个域
// 后面我们会讲WeakRef的实现,还会hack这个结构。
OopMapBlock* const start_map = start_of_nonstatic_oop_maps();
OopMapBlock* map = start_map + nonstatic_oop_map_count();
// 如果使用压缩指针,就会使用4字节,共计32位来存储对象的地址。
if (UseCompressedOops) {
while (start_map < map) {
–map;
// 重点来了,我们是倒着遍历这个对象的所有fields的。还记得我们之前讨论过的深
// 度优先遍历和广度优先遍历的问题吗?我们这里要把这些fields依次进栈,如果要
// 实现DFS,就必须逆向查找,这样才能使得前边的域放到栈顶,进而实现深度优先搜索
narrowOop * const start = (narrowOop *)(obj->obj_field_addr(map->offset()));
narrowOop * p = start + map->count();
while (start < p) {
–p;
assert_nothing§;
if (PSScavenge::should_scavenge§) {
pm->claim_or_forward_depth§;
}
}
}
} else {
while (start_map < map) {
–map;
oop * const start = (oop *)(obj->obj_field_addr(map->offset()));
oop * p = start + map->count();
while (start < p) {
–p;
assert_nothing§;
if (PSScavenge::should_scavenge§) {
pm->claim_or_forward_depth§;
}
}
}
}
}
上面的代码中,使用压缩指针和不使用压缩指针的代码其实是完全一样的,所不同的仅仅是类型而已。关于压缩指针,我这里就先不介绍了,只要知道它是一种使用32地址去存储对象指针的办法就可以了。
我们还是沿着主线往下走,继续看claim_or_forward_depth的实现:
template
inline void PSPromotionManager::claim_or_forward_depth(T* p) {
assert(PSScavenge::should_scavenge(p, true), “revisiting object?”);
assert(Universe::heap()->kind() == CollectedHeap::ParallelScavengeHeap,
“Sanity”);
assert(Universe::heap()->is_in§, “pointer outside heap”);
claim_or_forward_internal_depth§;
}
嗯。只有三个assert,还有一个调用,这个没什么东西,我们继续往下走:
template
inline void PSPromotionManager::claim_or_forward_internal_depth(T* p) {
if (p != NULL) { // XXX: error if p != NULL here
oop o = oopDesc::load_decode_heap_oop_not_null§;
if (o->is_forwarded()) {
o = o->forwardee();
// Card mark
if (PSScavenge::is_obj_in_young(o)) {
PSScavenge::card_table()->inline_write_ref_field_gc(p, o);
}
// 把 forward 指针(现在就是o),存到p所指的位置
oopDesc::encode_store_heap_oop_not_null(p, o);
} else {
// 如果还没有被forward,并不立即去做copy,而是放到一个栈里。所有的GC线程都会从这
// 个栈里去取任务。取出来以后再进行遍历和copy。这个过程我们就不分析了。
push_depth§;
}
}
}
理解这段代码的关键在于理解p和o所代表的意义。看下面这张图:
p其实是一个指针,它指向的地址上存着o,而o也是一个指针,它指向Java 堆中的真正的对象C。如果我们发现C已经被forward过了,那么C上必然存着一个forwarding指针,指向C’的位置。然后我们通过encode_store_heap_oop_not_null,把这个forwarding更新到p所指向的位置。
通过这个办法,当一个对象搬了家以后,我们就可以找出所有引用它的地方,然后把新的地址更新回去。
好了。关于copy gc,大概就讲这么多了。理解了这五篇文章,可以说对于Hotspot中的Young GC就基本学透了。
作业:
for (int i = 0; i < COUNT; i++) {
byte[] buf = new byte[MEGA];
}
然后通过
java -XX:+PrintGCDetails
观察垃圾回收的log