[讨论] 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后,对象的年龄是清零还是不变?
Global site tag (gtag.js) - Google Analytics