G1 Rset源码分析

卡表

G1堆中,卡表是由1字节元素组成的数组,数组里面的元素称为卡页,这个卡表会映射整个堆空间,每个卡页会对应堆中的512字节的空间。如下图,大小为1Mregion会对应2048个卡表,1GB的堆会存在2097152个卡表。
在这里插入图片描述

Rset

Rset用来记录跨对象间跨Region的引用,当虚拟机进行垃圾回收时,通过扫描Rset来判断被回收的对象是否被其他Region内的对象引用。

什么时候需要记录引用关系

不是所有的对象被其他对象引用都需要记录,如下是引用关系的归纳:

  • Region之间的对象引用
  • 新生代对象引用老年代对象
  • 新生代不同Region下的对象间发生引用关系
  • 老年代对象引用新生代对象
  • 老年代不同Region下的对象间发生引用

同一个Region中的对象如果发生引用,不管是老年代还是新生代,都不需要在Rset中记录引用关系,因为当一个Region被回收时,会扫描整个Region

新生代引用老年代对象时不需要记录引用关系。老年代进行垃圾回收前会进行一次年轻代的垃圾回收,并且将新生代存活的对象作为GC ROOT进行后续的标记。

非同一个Region的新生代对象间发生引用不需要记录到Rset。新生代垃圾回收采用的复制算法,JVM会扫描整个新生代堆区,然后把存活的对象复制到新生代存活区或者对象晋升到老年代。

老年代引用新生代对象时需要将引用关系记录到Rset中。新生代进行垃圾回收时,需要确认新生代的对象有没有被老年代的对象引用,通过Rset记录引用关系来避免扫描整个老年代堆区。

非同一个Region的老年代对象间发生引用时需要将引用关系记录到Rset中。老年代的回收不是全量的,每一次混合回收只是回收部分垃圾占比高的老年代Region堆,Rset避免扫描整个老年代堆区。

Point In Or Point Out

对象分配在Region中,而每个Region都会对应一个Rset,假设 A、B分别为老年代、新生代对象,对象A引用对象B,当将引用记录到A所对应的Rset中时,是一种Point Out方式,即我引用了谁,另一种方式Point In,谁引用了我。

新生代发生垃圾回收时,如果采用Point Out方式,那么需要扫描整个Rset来确认新生代对象是否被老年代对象引用,这种方式效率太低,而Point In方式,只需要扫描被回收对象所对应的Rset,就可以知道哪些对象引用了自己。显然G1采用了Point In方式。

Rset 数据结构

Rset是一个虚拟的概念,当一个对象被其他对象引用时,会通过一个Per Region Table来记录引用方的Region索引和卡表索引,并且随着Region引用次数变化,PRT的存储结构也会出现变化,如下是Per Region Table的三种数据结构:

//https://hg.openjdk.org/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/gc_implementation/g1/heapRegionRemSet.hpp

class OtherRegionsTable VALUE_OBJ_CLASS_SPEC {
  //粗粒度PRT 
  BitMap      _coarse_map;
  //细粒度PRT 
  PerRegionTable** _fine_grain_regions;
  //稀疏PRT
  SparsePRT   _sparse_table;
}

SparsePRT

SparsePRT 是稀疏PRT,它的数据结构是哈希表,key是引用方的Region Indexvalue是对象所在的卡页。

//https://hg.openjdk.org/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/gc_implementation/g1/sparsePRT.hpp

class RSHashTable : public CHeapObj<mtGC> {
  //哈希槽,它的索引=RegionIndex&capacity,value是_entries数组的索引,通过value可以获取_entries数组中的SparsePRTEntry对象
  int* _buckets;
  //_entries是SparsePRTEntry类型的数组,每一个槽位存储一个SparsePRTEntry对象
  SparsePRTEntry* _entries;
}

class SparsePRTEntry: public CHeapObj<mtGC> {
   
  //key 
  RegionIdx_t _region_ind;
  //解决哈希冲突,指向下一个SparsePRTEntry对象
  int         _next_index;
  //value 卡表索引,默认大小是4
  CardIdx_t   _cards[1];
}

举一个例子,假设A、B、C分别位于RegionA RegionB RegionCRegion堆被卡表映射,对象A在卡页0中,对象B在卡页4中,对象C在卡页8中,当对象B、C引用A时,RegionA对应的Rset会记录B、C所在的Region索引。

如下,Rset添加引用记录时,首先会在_entries数组中申请一个空间来存储SparsePRTEntry对象,然后将SparsePRTEntry对象的_cards来存储引用方的卡页,最后将SparsePRTEntry对象在_entries数组中的索引存储到_buckets[RegionIndex&capacity]处。

在这里插入图片描述

如下是SparsePRT添加引用的代码:

//https://hg.openjdk.org/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/gc_implementation/g1/sparsePRT.cpp

//region_ind region索引,card_index 卡表索引
bool RSHashTable::add_card(RegionIdx_t region_ind, CardIdx_t card_index) {
  //生成SparsePRTEntry对象
  //SparsePRTEntry对象的_region_ind变量会存储RegionIndex
  SparsePRTEntry* e = entry_for_region_ind_create(region_ind);
  //将引用方的卡页保存到SparsePRTEntry对象的_cards数组中
  SparsePRTEntry::AddCardResult res = e->add_card(card_index);
  //记录卡页数量
  if (res == SparsePRTEntry::added) _occupied_cards++;
  return res != SparsePRTEntry::overflow;
}
创建SparsePRTEntry对象

稀疏PRT在初始化时会创建一组连续的空间_entries,它里面存储的是SparsePRTEntry指针,通过该指针可以对SparsePRTEntry对象进行操作。

//https://hg.openjdk.org/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/gc_implementation/g1/sparsePRT.cpp

//创建SparsePRTEntry对象
SparsePRTEntry*
RSHashTable::entry_for_region_ind_create(RegionIdx_t region_ind) {
 //检查 region_ind是否保存过
  SparsePRTEntry* res = entry_for_region_ind(region_ind);
  if (res == NULL) {
    //申请空间,返回_entries数组的索引
    int new_ind = alloc_entry();
    //通过_entries的索引可以获取到SparsePRTEntry指针
    res = entry(new_ind);
    //通过指针对SparsePRTEntry对象进行初始化
    res->init(region_ind);
    //生成哈希槽_bucketsd的索引
    int ind = (int) (region_ind & capacity_mask());
    //如果有哈希冲突通过链表来解决
    res->set_next_index(_buckets[ind]);
    //将链表头节点存储到数组ind处
    _buckets[ind] = new_ind;
     //计数
    _occupied_entries++;
  }
  return res;
}
保存卡页

初始化SparsePRTEntry对象后将引用对象所在的卡页存储到SparsePRTEntry_cards数组中,该数组默认大小是4,如果数组满了之后,JVM会将稀疏PRT升级未细粒度PRT

//https://hg.openjdk.org/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/gc_implementation/g1/sparsePRT.cpp

SparsePRTEntry::AddCardResult SparsePRTEntry::add_card(CardIdx_t card_index) {
  //将Card Index 保存到_cards数组中
  //cards_num()返回数组的大小,它的默认值是4  
  CardIdx_t c;
  for (int i = 0; i < cards_num(); i += UnrollFactor) {
    c = _cards[i];
    //c == card_index 说明卡表已经被标记
    if (c == card_index) return found;
    //c == NullEntry 说明有空余的空间来标记引用方所在的卡表
    if (c == NullEntry) { _cards[i] = card_index; return added; }
    c = _cards[i + 1];
    if (c == card_index) return found;
    if (c == NullEntry) { _cards[i + 1] = card_index; return added; }
    c = _cards[i + 2];
    if (c == card_index) return found;
    if (c == NullEntry) { _cards[i + 2] = card_index; return added; }
    c = _cards[i + 3];
    if (c == card_index) return found;
    if (c == NullEntry) { _cards[i + 3] = card_index; return added; }
  }
  for (int i = 0; i < cards_num(); i++) {
    CardIdx_t c = _cards[i];
    if (c == card_index) return found;
    if (c == NullEntry) { _cards[i] = card_index; return added; }
  }
  //_cards 的默认大小是4,如果超过了容量大小,将尝试使用细粒度数据结构  
  return overflow;
}

细粒度PRT

稀疏PRT存储的是引用方对象所处的卡页索引,如果引用次数变多会浪费内存空间,所以默认情况下,稀疏PRT存储某个Region超过4个卡页就会采用细粒度PRT,原来的数据迁移到细粒度PRT

细粒度PRT不会存储卡页,而是将卡页对应的BitMap处设置为1

//https://hg.openjdk.org/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/gc_implementation/g1/heapRegionRemSet.hpp

class OtherRegionsTable VALUE_OBJ_CLASS_SPEC {
  //细粒度PRT哈希槽,
  PerRegionTable** _fine_grain_regions;
}

class PerRegionTable: public CHeapObj<mtGC> {
  //Region
  HeapRegion*     _hr;
  //用来标记引用方的卡表
  BitMap          _bm;
  //解决哈希冲突,同一个槽位使用_collision_list_next连接成链表
  PerRegionTable * _collision_list_next;        
}

细粒度PRT的结构图如下:

在这里插入图片描述

细粒度PRT保存索引
//https://hg.openjdk.org/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/gc_implementation/g1/heapRegionRemSet.cpp

void add_card_work(CardIdx_t from_card, bool par) {
    //_bm 是一个BitMap,用来记录卡页是否被标记过
    if (!_bm.at(from_card)) {
      //par 是否是并发环境
      if (par) {
        //将_bm的槽设置为1,表示某个卡页的引用  
        if (_bm.par_at_put(from_card, 1)) {
          Atomic::inc(&_occupied);
        }
      } else {
        _bm.at_put(from_card, 1);
        //用来记录region的引用次数
        //delete_region_table方法中会使用到
        //清理细粒度PRT时,优先清理_occupied大的PRT,
        _occupied++;
      }
    }
  }

粗粒度PRT

粗粒度PRT的元素由细粒度PRT升级而来,当细粒度PRT满了之后,会将引用最多的细粒度PRT删除,然后将该PRT持有的Region保存到全局BitMap中,整个PRT的数据结构维度由卡页变成Region

//https://hg.openjdk.org/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/gc_implementation/g1/heapRegionRemSet.cpp

PerRegionTable* OtherRegionsTable::delete_region_table() {

  PerRegionTable* max = NULL;
  jint max_occ = 0;
  PerRegionTable** max_prev;
  size_t max_ind;

  size_t i = _fine_eviction_start;
  for (size_t k = 0; k < _fine_eviction_sample_size; k++) {
    size_t ii = i;
    
    //获取细粒度PRT保存的引用  
    PerRegionTable** prev = &_fine_grain_regions[ii];
    PerRegionTable* cur = *prev;
    //找出occupied最大的region
    while (cur != NULL) {
      //Region被引用的次数
      jint cur_occ = cur->occupied();
      if (max == NULL || cur_occ > max_occ) {
        max = cur;
        max_prev = prev;
        max_ind = i;
        max_occ = cur_occ;
      }
      prev = cur->collision_list_next_addr();
      cur = cur->collision_list_next();
    }
  }
  
  size_t max_hrs_index = (size_t) max->hr()->hrs_index();
  //将occupied最大的region是否保存到粗粒度PRT中
  if (!_coarse_map.at(max_hrs_index)) {
    //将BitMap对应的max_hrs_index设置为true
    _coarse_map.at_put(max_hrs_index, true);
    _n_coarse_entries++;
  }
  //返回max,这个maxRegion后续会被初始化掉
  return max;
}

Rset 管理引用的完整代码

//https://hg.openjdk.org/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/gc_implementation/g1/heapRegionRemSet.cpp

//from 引用来源  tid 线程id
void OtherRegionsTable::add_reference(OopOrNarrowOopStar from, int tid) {
  //当前线程使用的heap region索引
  size_t cur_hrs_ind = (size_t) hr()->hrs_index();
  //获取持有引用的对象所在卡表
  int from_card = (int)(uintptr_t(from) >> CardTableModRefBS::card_shift);
  if (from_card == _from_card_cache[tid][cur_hrs_ind]) {
    return;
  } else {
    _from_card_cache[tid][cur_hrs_ind] = from_card;
  }
    
  //From Region
  HeapRegion* from_hr = _g1h->heap_region_containing_raw(from);
  //From Region Index
  RegionIdx_t from_hrs_ind = (RegionIdx_t) from_hr->hrs_index();

  //粗粒度prt  _coarse_map 是一个BitMap ,用来标记Region是否被标记  
  if (_coarse_map.at(from_hrs_ind)) {
    return;
  }
    
  //程序执行到此,说明Rset没有使用粗粒度的prt来保存引用
  size_t ind = from_hrs_ind & _mod_max_fine_entries_mask;
  //检查是否使用细粒度的PRT来保存引用
  PerRegionTable* prt = find_region_table(ind, from_hr);
  if (prt == NULL) {
   //存在并发情况,加锁
    MutexLockerEx x(&_m, Mutex::_no_safepoint_check_flag);
    //双重判断
    prt = find_region_table(ind, from_hr);
    if (prt == NULL) {

      uintptr_t from_hr_bot_card_index =
        uintptr_t(from_hr->bottom())
          >> CardTableModRefBS::card_shift;
      CardIdx_t card_index = from_card - from_hr_bot_card_index;
      // 如果允许使用稀疏PRT 
      if (G1HRRSUseSparseTable &&
          //尝试将卡页索引保存到稀疏PRT,key是RegionIndex,value 是CardIndex
          _sparse_table.add_card(from_hrs_ind, card_index)) {
        return;
      }
        
	   //如果添加稀疏PRT失败,将使用细粒度PRT保存引用
      if (_n_fine_entries == _max_fine_entries) {
        //如果细粒度PRT已经满了,将引用最多细粒度PRT删除,然后将其持有的Region保存到粗粒度PRT
        prt = delete_region_table();
        prt->init(from_hr, false);
      } else {
        //申请空间创建细粒度PRT
        prt = PerRegionTable::alloc(from_hr);
        link_to_all(prt);
      }
        
      //获取细粒度哈希槽ind处的元素
      PerRegionTable* first_prt = _fine_grain_regions[ind];
      //处于哈希冲突
      prt->set_collision_list_next(first_prt);
      _fine_grain_regions[ind] = prt;
      //计数  
      _n_fine_entries++;
	
     
      if (G1HRRSUseSparseTable) {
       //将稀疏PRT里面的数据迁移到细粒度卡表中
        SparsePRTEntry *sprt_entry = _sparse_table.get_entry(from_hrs_ind);
        for (int i = 0; i < SparsePRTEntry::cards_num(); i++) {
          CardIdx_t c = sprt_entry->card(i);
          if (c != SparsePRTEntry::NullEntry) {
            prt->add_card(c);
          }
        }
        // Now we can delete the sparse entry.
        bool res = _sparse_table.delete_entry(from_hrs_ind);
      }
    }
  }
    
  //将引用方的卡页索引保存到细粒度PRT的bitmap中
  prt->add_reference(from);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值