panda白话 - G1垃圾收集器 - Mixed GC - 初始标记(根分区扫描)源码分析

G1垃圾收集 - Mixed GC:

  • 初始标记阶段 - Initial Mark
  • 并发标记阶段 - Concurrent Mark
  • 最终标记阶段 - Remark
  • 清理阶段 - Clean Up

初始标记阶段 - Initial Mark - 根标记

特性:

  • STW
  • 以Young GC后的survivor分区作为根进行扫描
  • 扫描的是survivor 分区到老年代Region对象的引用
  • 所以Mixed GC 一定发生在Young GC 之后

根分区扫描步骤:

  • scanRootRegions扫描所有survivor分区
  • scanRootRegion 逐个region进行扫描处理
  • [bootom, top]区间内所有的对象grayRoot置灰
  • 置灰就是在NextBitMap位图中进行标记、统计存活对象大小

结果:

  • 将survivor region 中所有对象标记为根(置为灰色)
  • 扫描survivor region中
    • 所有指向老年代的
    • 在Young GC初始标记阶段标记的引用
    • 及引用指向的对象

扫描指向老年代的引用 - 理解:

RSet 只记录 O->Y (老年代到年轻代的引用) O->O(老年代到老年代的引用)详情请看这篇 ->>>>>Panda 白话 - G1垃圾收集器 之 RSet(Remembed Set)源码解读
所以针对老年代被年轻代引用的对象,在一次young GC中,存活对象会被复制到Survivor Region
所以需要扫描Survivor 分区的对象,作为根,来找到老年代被年轻代引用的对象,即存活对象

源码分析:

根分区扫描源码在:concurrentMark.cpp

void ConcurrentMark::scanRootRegions() {
  ClassLoaderDataGraph::clear_claimed_marks();
  //至少有一个分区需要扫描时,scan_in_progress会被设置为true
  //这里root_regions获取的是survivor分区
  if (root_regions()->scan_in_progress()) {
  	//获取并行标记线程数
    _parallel_marking_threads = calc_parallel_marking_threads();
    //标记线程数与1U取最大值
    uint active_workers = MAX2(1U, parallel_marking_threads());
    //创建并运行扫描任务
    CMRootRegionScanTask task(this);
    if (use_parallel_marking_threads()) {
      _parallel_workers->set_active_workers((int) active_workers);
      _parallel_workers->run_task(&task);
    } else {
      task.work(0);
    }
    //通知锁,可以进行下一次YGC
    root_regions()->scan_finished();
  }
}

scan_finished()动作是因为:

  • Mixed GC 依赖与Young GC后的Survivor Region
  • 如果在Mixed GC还没有结束,又发生了Young GC,那么Survivor Region就会变化,Mixed GC 是不能接收根发生变化的
  • 所以在Mixed GC时需保证根分区扫描完成才能再一次YGC
  • 同过锁通知机制完成
  • 所以本次根扫描完成后释放锁,通知可以进行下一次YGC

我们来向下追CMRootRegionScanTask的扫描任务方法:
CMRootRegionScanTask作为并发标记任务,会先扫描活跃根分区,然后对每个分区进行处理

void work(uint worker_id) {
    //cm - concurrentMark 并发标记线程
    //获取全部根 survivor region,
    //YGC结束时设置的待扫描的region
    CMRootRegions* root_regions = _cm->root_regions();
    HeapRegion* hr = root_regions->claim_next();
    //遍历所有根region
    while (hr != NULL) {
      //逐个region扫描
      _cm->scanRootRegion(hr, worker_id);
      hr = root_regions->claim_next();
    }
  }

继续向下追scanRootRegion方法:

void ConcurrentMark::scanRootRegion(HeapRegion* hr, uint worker_id) {
  //创建扫描根region的闭包
  G1RootRegionScanClosure cl(_g1h, this, worker_id);
  const uintx interval = PrefetchScanIntervalInBytes;
  //获取region的bottom
  HeapWord* curr = hr->bottom();
  //获取region的top
  const HeapWord* end = hr->top();
  //遍历整个有效分区,[bootom, top]region已经使用内存
  while (curr < end) {
    Prefetch::read(curr, interval);
    oop obj = oop(curr);
    int size = obj->oop_iterate(&cl);
    curr += size;
  }
}

继续向下追oop_iterate方法,最终调用**G1RootRegionScanClosure**do_oop_nv方法:

template <class T>
inline void G1RootRegionScanClosure::do_oop_nv(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);
    HeapRegion* hr = _g1h->heap_region_containing((HeapWord*) obj);
    //对象标记为灰色
    _cm->grayRoot(obj, obj->size(), _worker_id, hr);
  }
}

继续追grayRoot方法:

//置灰 - 对对象完成并发标记和计数
inline void ConcurrentMark::grayRoot(oop obj, size_t word_size,
                                     uint worker_id, HeapRegion* hr) {
  HeapWord* addr = (HeapWord*) obj;
  if (hr == NULL) {
    hr = _g1h->heap_region_containing_raw(addr);
  } else {
  }
  //next_top_at_mark_start 是Next TAMS指针位置
  if (addr < hr->next_top_at_mark_start()) {
    //判断地址是否在next位图中,在则已经标记过
    if (!_nextMarkBitMap->isMarked(addr)) {
        //不再位图中,则标记、统计
      par_mark_and_count(obj, word_size, hr, worker_id);
    }
  }
}

我们继续追par_mark_and_count方法:

inline bool ConcurrentMark::par_mark_and_count(oop obj,size_t word_size,HeapRegion* hr,uint worker_id) {
  HeapWord* addr = (HeapWord*)obj;
  //在NextBitMap 位图中进行标记,标记这个地址指向的对象是存活的
  if (_nextMarkBitMap->parMark(addr)) {
    MemRegion mr(addr, word_size);
    //这个方法会根据worker_id找到当前的任务类,会记录当前任务类标记处理的统计数据
    //记录这个对象所在的卡表有效,标记为1
    count_region(mr, hr, worker_id);
    return true;
  }
  return false;
}
count_region 计数的作用:
因为是并发标记,多个线程访问自己位图,数据不共享,
计数来计算存活对象的大小
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值