1. 简介
在上篇文章中,我们介绍了RSet的原理,当对象引用关系变化时,都需要更新RSet。为了不影响Mutator的性能,RSet的更新通常是异步进行的,这一异步更新操作需要引入DCQS(Dirty Card Queue Set)结构。
本文将分析DCQS的原理。
2. DCQS
2.1 写入DCQS
JVM声明了一个全局的静态结构G1BarrierSet,其中包含两个Queue Set,DirtyCardQueueSet和G1SATBMarkQueueSet,分别用于处理DCQS和STAB。
g1BarrierSet.hpp
class G1BarrierSet: public CardTableBarrierSet {
friend class VMStructs;
private:
BufferNode::Allocator _satb_mark_queue_buffer_allocator;
BufferNode::Allocator _dirty_card_queue_buffer_allocator;
G1SATBMarkQueueSet _satb_mark_queue_set;
DirtyCardQueueSet _dirty_card_queue_set;
}
- _dirty_card_queue_set即为全局的DCQS
g1BarrierSet.cpp
G1BarrierSet::G1BarrierSet(G1CardTable* card_table) :
CardTableBarrierSet(make_barrier_set_assembler<G1BarrierSetAssembler>(),
make_barrier_set_c1<G1BarrierSetC1>(),
make_barrier_set_c2<G1BarrierSetC2>(),
card_table,
BarrierSet::FakeRtti(BarrierSet::G1BarrierSet)),
_satb_mark_queue_buffer_allocator(G1SATBBufferSize, SATB_Q_FL_lock),
_dirty_card_queue_buffer_allocator(G1UpdateBufferSize, DirtyCardQ_FL_lock),
_satb_mark_queue_set(),
_dirty_card_queue_set()
{}
- DCQ队列长度为G1UpdateBufferSize(默认256),超过阈值时该DCQ会被写入DCQS
DCQ的入队逻辑也在g1BarrierSet.cpp
void G1BarrierSet::write_ref_field_post_slow(volatile jbyte* byte) {
// In the slow path, we know a card is not young
assert(*byte != G1CardTable::g1_young_card_val(), "slow path invoked without filtering");
OrderAccess::storeload();
if (*byte != G1CardTable::dirty_card_val()) {
*byte = G1CardTable::dirty_card_val();
Thread* thr = Thread::current();
if (thr->is_Java_thread()) {
G1ThreadLocalData::dirty_card_queue(thr).enqueue(byte);
} else {
MutexLockerEx x(Shared_DirtyCardQ_lock,
Mutex::_no_safepoint_check_flag);
_dirty_card_queue_set.shared_dirty_card_queue()->enqueue(byte);
}
}
}
- 如果当前线程是Mutator线程,则调用当前线程DCQ的enqueue方法
- 否则,调用DCQS的_shared_dirty_card_queue的enqueue方法,_shared_dirty_card_queue是全局变量,调用enqueue前需要加锁
入队最后都会调用到DirtyCardQueue的enqueue方法,DirtyCardQueue是PtrQueue的子类,因此实际会调用PtrQueue的enqueue方法。
ptrQueue.hpp
void enqueue(void* p