ZGC源码解析(三)
2023-03-30 本文已影响0人
allanYan
阶段3( Pause Mark End)
接上文,当并发标记完成之后,接下来会试着结束标记阶段,我们知道并发标记阶段标记线程和业务线程是同时在允许着的,就像一边打扫房间,一边仍垃圾;因此需要一个暂停业务线程的阶段,来彻底的完成打扫阶段;
// Phase 3: Pause Mark End
while (!pause_mark_end()) {
// Phase 3.5: Concurrent Mark Continue
concurrent(mark_continue);
}
可以看到ZDriver线程会不断尝试结束标记阶段,如果不能结束,则重新标记一次;标记的代码见 concurrent_mark_continue;
void ZDriver::concurrent_mark_continue() {
ZStatTimer timer(ZPhaseConcurrentMarkContinue);
ZHeap::heap()->mark(false /* initial */);
}
concurrent_mark_continue的执行逻辑和并发标记的代码是一样的,只是不需要GC ROOTS标记,因此initial参数传入的是false;
接下来我们仔细看看pause_mark_end都做了些什么:
bool ZMark::try_end() {
// 将线程上的标记栈数据ZMarkThreadLocalStacks刷新到全局的条带中去
if (!flush(true /* at_safepoint */)) {
//如果发现线程的标记栈数据都是空的,意味着没有新的垃圾,标记阶段可以结束
return true;
}
return try_complete();
}
bool ZMark::try_complete() {
_ntrycomplete++;
//如果条带中还有数据,则再执行一次并发标记,但是只执行200微秒
ZMarkTask task(this, ZMarkCompleteTimeout);
_workers->run(&task);
return _stripes.is_empty();//如果条带为空,则可以结束标记阶段
}
看到这里,可能大家还是有个疑问,标记阶段结束之后,GC的整个阶段并没有完成,如果在这个阶段,有业务线程重新引用了某个未被标记的对象,那不是会产生问题吗?那么ZGC是怎么解决这类问题的呢?答案就是读屏障;
inline oop ZBarrierSet::AccessBarrier<decorators, BarrierSetT>::oop_load_in_heap(T* addr) {
verify_decorators_absent<ON_UNKNOWN_OOP_REF>();
const oop o = Raw::oop_load_in_heap(addr);
return load_barrier_on_oop_field_preloaded(addr, o);
}
inline oop ZBarrier::weak_load_barrier_on_oop_field_preloaded(volatile oop* p, oop o) {
return weak_barrier<is_weak_good_or_null_fast_path, weak_load_barrier_on_oop_slow_path>(p, o);
}
template <ZBarrierFastPath fast_path, ZBarrierSlowPath slow_path>
inline oop ZBarrier::weak_barrier(volatile oop* p, oop o) {
const uintptr_t addr = ZOop::to_address(o);
// Fast path
if (fast_path(addr)) {
// Return the good address instead of the weak good address
// to ensure that the currently active heap view is used.
return ZOop::from_address(ZAddress::good_or_null(addr));
}
// Slow path
const uintptr_t good_addr = slow_path(addr);
if (p != NULL) {
// The slow path returns a good/marked address or null, but we never mark
// oops in a weak load barrier so we always heal with the remapped address.
self_heal<fast_path>(p, addr, ZAddress::remapped_or_null(good_addr));
}
return ZOop::from_address(good_addr);
}
简单的概括下,业务线程在访问变量的时候,会标记使用到的对象,这样就不会出现漏标的情况了;
阶段4(Concurrent Mark Free)
void ZMark::free() {
// Free any unused mark stack space
_allocator.free();
// Update statistics
ZStatMark::set_at_mark_free(_allocator.size());
}
这个阶段做的事情比较简单,只是释放标记栈空间同时更新统计数据
阶段5(Concurrent Process Non-Strong References)
在阶段2.2 ( Mark)介绍过,并发标记阶段,GC线程会标记各类Reference对象,这些发现的引用(_discovered_list)就是在阶段5进行进一步处理的:
void ZHeap::process_non_strong_references() {
// 处理 Soft/Weak/Final/Phantom引用,和业务线程并发执行
_reference_processor.process_references();
_weak_roots_processor.process_weak_roots();
_unload.unlink();
ZRendezvousClosure cl;
Handshake::execute(&cl);
ZResurrection::unblock();
_unload.purge();
_reference_processor.enqueue_references();
}
void ZReferenceProcessor::work() {
// _discovered_list是个链表,通过Reference对象的discovered字段链接起来
oop* const list = _discovered_list.addr();
oop* p = list;
while (*p != NULL) {
const oop reference = *p;
const ReferenceType type = reference_type(reference);
if (should_drop(reference, type)) {//如果referent为空或者referent是活跃的,从队列中删除reference
*p = drop(reference, type);
} else {
p = keep(reference, type);//如果referent不活跃了,需要将Reference对象的_referent设置为null
}
}
// 将上述处理后的_discovered_list添加到_pending_list
if (*list != NULL) {
*p = Atomic::xchg(_pending_list.addr(), *list);
if (*p == NULL) {
// First to prepend to list, record tail
_pending_list_tail = p;
}
// Clear discovered list
*list = NULL;
}
}
经过上述处理,需要回收的Reference对象都会加入到pending_list链表,并通过Universe::swap_reference_pending_list更新全局变量_reference_pending_list;
java.lang.ref.Reference中有个ReferenceHandler线程,会取出_reference_pending_list中的数据,执行入列动作;