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 计数的作用:
因为是并发标记,多个线程访问自己位图,数据不共享,
计数来计算存活对象的大小