1. 简介
并发标记是ZGC的第2个阶段,此阶段中,GC worker与Mutator并发工作,GC worker执行标记、load barrier和relocate。
并发线程从带标记对象列表开始,递归遍历对象及对象的成员变量并标记。
2. 源码分析
2.1 入栈
标记栈的size受ZMarkStackSpaceLimit参数控制,默认8G,合法值32MB到1TB之间。
share/gc/z/z_globals.hpp
product(size_t, ZMarkStackSpaceLimit, 8*G,
"Maximum number of bytes allocated for mark stacks")
range(32*M, 1024*G)
标记栈元素分为两种:
- 如果是普通对象,则直接入栈、递归标记即可。
- 如果是数组对象,全部入栈则可能引起溢出,需判断数组大小是否超过ZMarkPartialArrayMinSize(默认4KB),未超过则入栈标记,否则按ZMarkPartialArrayMinSize切分后入栈标记。
share/gc/z/zMarkStackEntry.hpp
ZMarkStackEntry(uintptr_t object_address, bool follow, bool finalizable) :
_entry(field_object_address::encode(object_address) |
field_follow::encode(follow) |
field_partial_array::encode(false) |
field_finalizable::encode(finalizable)) {}
ZMarkStackEntry(size_t partial_array_offset, size_t partial_array_length, bool finalizable) :
_entry(field_partial_array_offset::encode(partial_array_offset) |
field_partial_array_length::encode(partial_array_length) |
field_partial_array::encode(true) |
field_finalizable::encode(finalizable)) {}
share/gc/z/zMark.cpp
void ZMark::push_partial_array(uintptr_t addr, size_t size, bool finalizable) {
assert(is_aligned(addr, ZMarkPartialArrayMinSize), "Address misaligned");
ZMarkThreadLocalStacks* const stacks = ZThreadLocalData::stacks(Thread::current());
ZMarkStripe* const stripe = _stripes.stripe_for_addr(addr);
const uintptr_t offset = ZAddress::offset(addr) >> ZMarkPartialArrayMinSizeShift;
const uintptr_t length = size / oopSize;
const ZMarkStackEntry entry(offset, length, finalizable);
log_develop_trace(gc, marking)("Array push partial: " PTR_FORMAT " (" SIZE_FORMAT "), stripe: " SIZE_FORMAT,
addr, size, _stripes.stripe_id(stripe));
stacks->push(&_allocator, &_stripes, stripe, entry, false /* publish */);
}
2.2 标记
标记的流程图大致如下
share/gc/z/zGlobal.cpp
// Global phase state
extern uint32_t ZGlobalPhase;
const uint32_t ZPhaseMark = 0;
const uint32_t ZPhaseMarkCompleted = 1;
const uint32_t ZPhaseRelocate = 2;
share/gc/z/zBarrier.inline.hpp
inline bool ZBarrier::during_relocate() {
return ZGlobalPhase == ZPhaseRelocate;
}
如上述代码所示,判断转移还是标记,其实就是根据全局变量ZGlobalPhase的值确定的。
share/gc/z/zMark.inline.hpp
template <bool follow, bool finalizable, bool publish>
inline void ZMark::mark_object(uintptr_t addr) {
assert(ZAddress::is_marked(addr), "Should be marked");
ZMarkThreadLocalStacks* const stacks = ZThreadLocalData::stacks(Thread::current());
ZMarkStripe* const stripe = _stripes.stripe_for_addr(addr);
ZMarkStackEntry entry(addr, follow, finalizable);
stacks->push(&_allocator, &_stripes, stripe, entry, publish);
}
- 将对象压入标记栈
share/gc/z/zMark.cpp
void ZMark::work_without_timeout(ZMarkCache* cache, ZMarkStripe* stripe, ZMarkThreadLocalStacks* stacks) {
ZStatTimer timer(ZSubPhaseConcurrentMark);
ZMarkNoTimeout no_timeout;
for (;;) {
drain_and_flush(stripe, stacks, cache, &no_timeout);
if (try_steal(stripe, stacks)) {
// Stole work
continue;
}
if (try_proactive_flush()) {
// Work available
continue;
}
if (try_terminate()) {
// Terminate
break;
}
}
}
调用链非常复杂,work_without_timeout -> drain_and_flush -> mark_and_follow,mark_and_follow是实际标记逻辑。
share/gc/z/zMark.cpp
void ZMark::mark_and_follow(ZMarkCache* cache, ZMarkStackEntry entry) {
// Decode flags
const bool finalizable = entry.finalizable();
const bool partial_array = entry.partial_array();
if (partial_array) {
// 之前入栈的并行数组对象的处理
follow_partial_array(entry, finalizable);
return;
}
const uintptr_t addr = entry.object_address();
if (!try_mark_object(cache, addr, finalizable)) {
// 已标记对象,直接退出
return;
}
if (is_array(addr)) {
// 数组对象
const bool follow = entry.follow();
if (follow) {
// 标记数组对象
follow_array_object(objArrayOop(ZOop::from_address(addr)), finalizable);
}
} else {
// 标记普通对象
// 进入load barrier代码标记
follow_object(ZOop::from_address(addr), finalizable);
}
}
share/gc/z/zMark.cpp
void ZMark::follow_array(uintptr_t addr, size_t size, bool finalizable) {
// 是否小于4KB
if (size <= ZMarkPartialArrayMinSize) {
// 小数组直接标记
follow_small_array(addr, size, finalizable);
} else {
// 大数组分段入栈
follow_large_array(addr, size, finalizable);
}
}
- 标记数组对象,根据数组的size分为大数组和小数组。
2.3 计数
并发标记阶段,除了标记之外,还需要统计活跃对象的个数和活跃对象占用空间,ZGC使用ZLiveMap维护活跃对象信息。
share/gc/z/zLiveMap.hpp
class ZLiveMap {
friend class ZLiveMapTest;
private:
// 分64段,便于多线程处理
static const size_t nsegments = 64;
// 页面的纪元,用于分辨页面是本轮GC启动前分配的还是启动后分配的
volatile uint32_t _seqnum;
// 活跃对象个数
volatile uint32_t _live_objects;
// 活跃对象size
volatile size_t _live_bytes;
// 记录分段的存活状态
BitMap::bm_word_t _segment_live_bits;
// 记录分段的回收状态
BitMap::bm_word_t _segment_claim_bits;
// 页面标记位图,ZLiveMap最重要的字段,虽然1bit就可以标记一个对象,但为了区分强引用和弱引用,使用2bit标记一个对象
ZBitMap _bitmap;
size_t _segment_shift;
}
_bitmap位图的大小由Page type决定:
share/gc/z/zPage.inline.hpp
ZPage::ZPage(uint8_t type, const ZVirtualMemory& vmem, const ZPhysicalMemory& pmem) :
_type(type),
_numa_id((uint8_t)-1),
_seqnum(0),
_virtual(vmem),
_top(start()),
// 创建ZLiveMap
_livemap(object_max_count()),
_last_used(0),
_physical(pmem),
_node() {
assert_initialized();
}
inline uint32_t ZPage::object_max_count() const {
switch (type()) {
case ZPageTypeLarge:
// large page永远只包含一个对象,所以返回1
return 1;
default:
// small和medium page按最小对象size计算
// small page对象按8字节对齐,2MB/8B = 262144
// medium page对象最小256KB,32MB/256KB = 128
return (uint32_t)(size() >> object_alignment_shift());
}
}
share/gc/z/zLiveMap.cpp
static size_t bitmap_size(uint32_t size, size_t nsegments) {
// object_max_count()计算的size和64取最大值后乘2
// 即为bitmap的size
return MAX2<size_t>(size, nsegments) * 2;
}
按如下方式区分强引用和弱引用
share/gc/z/zPage.inline.hpp
// 对象是否被标记, index
inline bool ZPage::is_object_marked(uintptr_t addr) const {
const size_t index = ((ZAddress::offset(addr) - start()) >> object_alignment_shift()) * 2;
return _livemap.get(index);
}
// 对象是否被强引用标记, index+1
inline bool ZPage::is_object_strongly_marked(uintptr_t addr) const {
const size_t index = ((ZAddress::offset(addr) - start()) >> object_alignment_shift()) * 2;
return _livemap.get(index + 1);
}
3. 总结
并发标记阶段主要是根据初始标记从GC roots找到的对象,使用深度优先遍历对象的成员变量进行标记,并记录活跃对象位图。