[讨论] HotSpot VM Serial GC的一个问题
RednaxelaFX
2014-01-22
你得了解CardTable是用来干嘛的。
如果有这样的对象关系: Object young_gen_object = new Object(); old_gen_object.field = young_gen_object; 那么是old_gen_object所在的地方对应的card会被dirty掉,因为是它持有跨代引用。 至于young_gen_object具体在什么地址都没关系,只要在young generation里。它对应的card不会被dirty。 所以在minor GC的时候,如果young generation里有对象移动到to-space了,那原本在old generation里的CardTable完全不受影响。 LeafInWind 写道 这个移动我觉得应该是不需要检查跨代引用及修改card table的。因为这些对象移动后都还在save mark和top之间,因此oop_since_save_mark_iterate方法能够完成对对象中所有oop field的scan,不需要借助card table。
但你不记得还会有下一次minor GC⋯下次就要用到新dirty的card了 LeafInWind 写道 另外从代码看,移动对象使用的始终是gc_barrier为false的FastScanClosure,因此也不会修改card table。
你没看到这个: FastScanClosure fsc_with_gc_barrier(this, true); 以及后面FastEvacuateFollowersClosure evacuate_followers的构造、gch->gen_process_strong_roots()的参数都有用到带GC barrier的版本 |
|
LeafInWind
2014-01-22
R神 写道 那么是old_gen_object所在的地方对应的card会被dirty掉,因为是它持有跨代引用。
至于young_gen_object具体在什么地址都没关系,只要在young generation里。它对应的card不会被dirty。 这个R神是对的,的确是我理解错了。但gen_process_strong_roots中如下这段代码 older_gens->set_generation(_gens[i]); rem_set()->younger_refs_iterate(_gens[i], older_gens); older_gens->reset_generation(); 我仍然认为是先清card,然后如果对象仍然在young gen中,则将相同的card置dirty,这是靠older_gens完成的,而如果对象移到old gen了,older_gens就不会再置了。 另外SharedHeap::process_strong_roots方法中,虽然not_older_gens和older_gens两个FastScanClosure都传过去了,但至少移动栈上引用时用的都是not_older_gens,因此即使对象移到了old gen,也不会去修改相应card。 但 R神 写道 但你不记得还会有下一次minor GC⋯下次就要用到新dirty的card了
似乎又是对的,这里感觉有点想不通了 哈哈,想通了。SharedHeap::process_strong_roots方法中,对象移动到old gen时,的确不会将相应card置dirty,实际上它也置不了,还没遍历oop呢,是否是跨代引用都不知道。但evacuate_followers.do_void()在处理old gen时使用的是fsc_with_gc_barrier,此时就会发现跨代引用并置相应card为dirty了。 |
|
RednaxelaFX
2014-01-23
LeafInWind 写道 RednaxelaFX 写道 那么是old_gen_object所在的地方对应的card会被dirty掉,因为是它持有跨代引用。
至于young_gen_object具体在什么地址都没关系,只要在young generation里。它对应的card不会被dirty。 这个R神是对的,的确是我理解错了。但gen_process_strong_roots中如下这段代码 older_gens->set_generation(_gens[i]); rem_set()->younger_refs_iterate(_gens[i], older_gens); older_gens->reset_generation(); 我仍然认为是先清card,然后如果对象仍然在young gen中,则将相同的card置dirty,这是靠older_gens完成的,而如果对象移到old gen了,older_gens就不会再置了。 这个理解是对的。实际动作是先清了card再扫描它,扫描过程中如果发现活对象涉及跨代引用,那对应的card就会被再次dirty。结果来看很多dirty card就还是dirty的。 |
|
LeafInWind
2014-01-24
这两天看了一下full gc,能否请教R神一个问题:
make-compact的第三步,调整所有的oop指向第二步中计算出的新位置,在SCAN_AND_ADJUST_POINTERS这个宏的开头,有如下代码: HeapWord* q = bottom(); HeapWord* t = _end_of_live; if (q < t && _first_dead > q && !oop(q)->is_gc_marked()) { /* we have a chunk of the space which hasn't moved and we've * reinitialized the mark word during the previous pass, so we can't * use is_gc_marked for the traversal. */ HeapWord* end = _first_dead; ... } 我理解_first_dead是一个指向当前contiguous space中第一个dead obj的指针,那么如果q<_first_dead的话,q就应该是live obj,那么相应的“oop(q)->is_gc_marked()”这个条件就应该一定成立,那么上面这段代码就毫无意义了。 从代码中的注释看,[bottom, _first_dead)这段区间似乎有一点特殊,但不知特殊在哪里??不知R神能否指点一下,谢谢! 又看了一下代码,现在基本想明白了。[bottom, _first_dead)这段区间中的对象的确比较特殊,特殊就特殊在它们虽然活着,但compact的时候并不需要移动。因此虽然在mark-compact的第一个阶段被标上了gc mark,但在第二阶段计算移动后新位置时又故意把gc mark去掉了。代码如下: HeapWord* CompactibleSpace::forward(...){ ... if ((HeapWord*)q != compact_top) { q->forward_to(oop(compact_top)); assert(q->is_gc_marked(), "encoding the pointer should preserve the mark"); } else { //q==compact_top说明当前对象不需要移动 // if the object isn't moving we can just set the mark to the default // mark and handle it specially later on. q->init_mark(); assert(q->forwardee() == NULL, "should be forwarded to NULL"); } } 所有在[bottom, _first_dead)这段区间中的对象虽然是活对象,但并没有gc mark。但尽管没有gc mark,其中的oop还是需要adjust的。所以才有SCAN_AND_ADJUST_POINTERS宏开头的一大段代码。 请教R神,上述理解有什么问题吗?! |
|
LeafInWind
2014-01-25
还是有一个问题,一次full gc过后,存活对象的年龄要长一岁吗,如果长的话,那young gen里超过阈值的对象是否在full gc过程中也应该promote到old gen去了呢???
|
|
RednaxelaFX
2014-01-25
LeafInWind 写道 还是有一个问题,一次full gc过后,存活对象的年龄要长一岁吗,如果长的话,那young gen里超过阈值的对象是否在full gc过程中也应该promote到old gen去了呢???
一次full GC过后,在理想情况下young generation里所有活的对象都被晋升到old generation了,不论age如何。 就是说只要old generation还有地方,young generation所有活对象都要拷贝过去。 HotSpot VM里对象的age只是经历的minor GC的次数。 |
|
LeafInWind
2014-01-25
感谢R神,之前没有细想,还以为是各个gen各自压缩的。又看了代码,由于第二阶段计算移动后新位置的过程中使用的始终是同一个CompactPoint对象,因此按照CompactibleSpace::forward中的如下代码:
size_t compaction_max_size = pointer_delta(end(), compact_top); while (size > compaction_max_size) { // switch to next compaction space cp->space->set_compaction_top(compact_top); cp->space = cp->space->next_compaction_space(); if (cp->space == NULL) { cp->gen = GenCollectedHeap::heap()->prev_gen(cp->gen); assert(cp->gen != NULL, "compaction must succeed"); cp->space = cp->gen->first_compaction_space(); assert(cp->space != NULL, "generation must have a first compaction space"); } compact_top = cp->space->bottom(); cp->space->set_compaction_top(compact_top); cp->threshold = cp->space->initialize_threshold(); compaction_max_size = pointer_delta(cp->space->end(), compact_top); } 应该是将old gen和young gen作为一个整体,将其中的存活对象依次压满old gen,然后是eden和from。 请教R神,不知我的补充是否正确。 另外,还想请教一下full gc和minor gc是什么关系,full gc是否会伴随minor gc。 还有,一次full gc后,对象的年龄是清零还是不变? |