先上一张Panda总结图,欢迎大佬们指正:
DCQ - Dirty Catd Queue
DCQS - Dirty Catd Queue Set
Mutator - 用户线程
RSet - Remember Set
Refinement - 优化线程
图描述:
-
每个Mutator(用户线程)私有一个DCQ,默认长度256,即可以记录256条引用记录,可以通过
G1UpdateBufferSize
参数修改 -
当用户线程修改代间引用关系(O->Y,O->O)
panda.kongfu = new Kongfu();
,写屏障会记录该引用记录到DCQ -
当DCQ记录数达到阈值(256)则将DCQ加入到DCQS,mutator重新申请一个DCQ
-
Refinement线程处理DQCS中DCQ
-
DCQS所有处理引用线程共享
-
DCQS分为4个Zone:白、绿、黄、红
-
Green : Yellow : Red = 1 : 3 : 6
-
DCQS 初始化时定义了一个全局静态的Monitor,所有Mutator可以访问
-
当DCQS使用GreenZone时,Mutator线程会notify 0号Refine线程启动处理DCQ,0号线程会notify 1号Refine,1号notify 2号。。。(最后一个Refine线程处理抽样)
-
Refine线程数可以由
G1ConcRefinementThreads
设置,未设置 则 =ParallelGCThreads
-
当DCQS使用YellowZone时,所有Refine线程启动处理DCQ
-
当DCQS使用RedZone时,Mutator线程也加入Refine工作
-
Refine线程启动处理DCQ,根据引用记录更新RSet
-
RSet随着引用数增多,切换数据结构 SparseTable(稀疏哈希表) ->PerRegionTable(细粒度PRT) ->BitMap(粗粒度位图)
整体框架脑子里有个概念了,下面开始庖丁解牛~
Refine线程:
- G1引入的并发线程池
- 线程数 = G1ConcRefinementThreads+1 (默认)
Refine线程的功能:
- 处理新生代分区的抽样 - 更新YHR(Young Heap Region)的数目
- 管理RSet
处理新生代分区的抽样:
功能:
设置YHR - 新生代分区的个数,使G1满足GC的预测停顿时间-XX:MaxGCPauseMillis
抽样方法 - 关键源码:concurrentG1RefineThread.cpp
void ConcurrentG1RefineThread::run_young_rs_sampling() {
// 获取DCQS - 线程共享
DirtyCardQueueSet& dcqs = JavaThread::dirty_card_queue_set();
while(!_should_terminate) {
//采样方法
sample_young_list_rs_lengths();
//加锁 - 线程共享、并发
MutexLockerEx x(_monitor, Mutex::_no_safepoint_check_flag);
if (_should_terminate) {
break;
}
//G1ConcRefinementServiceIntervalMillis - 采样间隔时间 - 控制抽样频度
_monitor->wait(Mutex::_no_safepoint_check_flag, G1ConcRefinementServiceIntervalMillis);
}
}
源码关键信息:
- 循环采样
- 采样方法
sample_young_list_rs_lengths();
- 每次采样间隔时间
G1ConcRefinementServiceIntervalMillis
继续追采样方法sample_young_list_rs_lengths():
// Refine 线程采样方法
void ConcurrentG1RefineThread::sample_young_list_rs_lengths() {
SuspendibleThreadSetJoiner sts;
//获取G1管理的堆空间
G1CollectedHeap* g1h = G1CollectedHeap::heap();
G1CollectorPolicy* g1p = g1h->g1_policy();
if (g1p->adaptive_young_list_length()) {
int regions_visited = 0;
//初始化一个年轻代分区集合
g1h->young_list()->rs_length_sampling_init();
//循环将抽样分区加入到新生代分区集合
while (g1h->young_list()->rs_length_sampling_more()) {
g1h->young_list()->rs_length_sampling_next();
++regions_visited;
//每次处理10个分区、主动让出cpu
if (regions_visited == 10) {
if (sts.should_yield()) {
sts.yield();
// we just abandon the iteration
break;
}
regions_visited = 0;
}
}
//用上面抽样数据更新YHR数目
g1p->revise_young_list_target_length_if_necessary();
}
}
追更新YHR数据方法revise_young_list_target_length_if_necessary()
:g1CollectorPolicy.cpp
void G1CollectorPolicy::revise_young_list_target_length_if_necessary() {
guarantee( adaptive_young_list_length(), "should not call this otherwise" );
//获取上面采样数目
size_t rs_lengths = _g1->young_list()->sampled_rs_lengths();
if (rs_lengths > _rs_lengths_prediction) {
// add 10% to avoid having to recalculate often
// 增加10%的冗余,避免多次重复计算
size_t rs_lengths_prediction = rs_lengths * 1100 / 1000;
//更新数目
update_young_list_target_length(rs_lengths_prediction);
}
}
管理RSet:
-
RSet用于管理对象的引用关系
-
Refine线程更新RSet
-
异步处理
-
G1先将所有引用关系放到DCQ - Dirty Card Queue
-
每个Mutator都有一个私有DCQ,默认256个、可由参数
G1UpdateBufferSize
指定 -
Refine 线程消费DCQS完成RSet 更新
-
G1声明了一个全局静态变量DCQS-DirtyCardQueueSet,存放DCQ
-
DCQ 通过 DCQS - Dirty Card Queue Set 管理
-
所有Refine线程共享DCQS
-
每个Mutator-用户线程初始化时都关联DCQS
-
Refine线程并发处理,每个Refine线程只负责几个DCQ
-
Refine线程池中最后一个线程为-抽样线程
源码解读:
DCQS 初始化 : g1CollectedHeap.cpp
dirty_card_queue_set().initialize(NULL, // 只能由JVM完成初始化动作
DirtyCardQ_CBL_mon,//全局Monitor
DirtyCardQ_FL_lock,
-1, // 设置无需处理
-1, // 不限制DCQS长度
Shared_DirtyCardQ_lock,
&JavaThread::dirty_card_queue_set()//java线程关联上DCQS,每个JavaTHread都可以通过dirty_card_queue_set()方法拿到DCQS,获取静态变量Monitor,然后通过Monitor 通知notify Refine 0号线程启动处理DCQ,Refine 0 号线程只能由Matutor线程唤醒
);
将对象放入DCQ中 : ptrQueue.hpp
// Enqueues the given "obj".
void enqueue(void* ptr) {
if (!_active) return;
else enqueue_known_active(ptr);
}
可以看到主要逻辑在 enqueue_known_active(ptr)
:
- DCQ满 则 申请新的DCQ
handle_zero_index()
- DCQ未满 则 加入到buff数组
void PtrQueue::enqueue_known_active(void* ptr) {
//DCQ满了
while (_index == 0) {
//将DCQ加入到DCQS 申请新的DCQ
handle_zero_index();
}
//DCQ有可用空间
//可用空间 -1
_index -= oopSize;
//将prt放入缓冲区中
_buf[byte_index_to_index((int)_index)] = ptr;
}
DCQ 加入 DCQS: handle_zero_index()
- 再次判断是否已申请,申请则返回
- 加锁入队
locking_enqueue_completed_buffer
- 不加锁入队
process_or_enqueue_complete_buffer
void PtrQueue::handle_zero_index() {
//二次判断、防止同一线程多次进入分配
if (_buf != NULL) {
//如果已经申请过了,直接返回
if (!should_enqueue_buffer()) {
return;
}
// 处理全局DCQS 加锁
if (_lock) {
void** buf = _buf; // local pointer to completed buffer
_buf = NULL; // clear shared _buf field
locking_enqueue_completed_buffer(buf); // 加锁处理入队
//buff不为空说明申请成功,返回
if (_buf != NULL) return;
} else {
//不加锁申请DCQ
if (qset()->process_or_enqueue_complete_buffer(_buf)) {
// Recycle the buffer. No allocation.
_sz = qset()->buffer_size();
_index = _sz;
return;
}
}
}
// 为 DCQ申请新的空间
_buf = qset()->allocate_buffer();
_sz = qset()->buffer_size();
_index = _sz;
}
不加锁入队 - process_or_enqueue_complete_buffer:
- 用户线程 -
mut_process_buffer
- 非用户线程 -
enqueue_complete_buffer
//处理DCQ - 根据情况判断是否需要Mutator介入
bool PtrQueueSet::process_or_enqueue_complete_buffer(void** buf) {
//判断当前线程是否是用户线程-mutator
if (Thread::current()->is_Java_thread()) {
// We don't lock. It is fine to be epsilon-precise here.
// 此处不上锁,允许竞争,最坏也就是mutator线程处理
if (_max_completed_queue == 0 || _max_completed_queue > 0 &&
_n_completed_buffers >= _max_completed_queue + _completed_queue_padding) {
//用户线程处理buff
bool b = mut_process_buffer(buf);
if (b) {return true;}
}
}
// The buffer will be enqueued. The caller will have to get a new one.
enqueue_complete_buffer(buf);
return false;
}
可以看到真正将DCQ -> DCQS 的逻辑在方法 enqueue_complete_buffer:
整体思想就是:将DCQ加入到DCQS链尾
void PtrQueueSet::enqueue_complete_buffer(void** buf, size_t index) {
//处理DCQS 上锁
MutexLockerEx x(_cbl_mon, Mutex::_no_safepoint_check_flag);
//从buffer新申请一个节点
BufferNode* cbn = BufferNode::new_from_buffer(buf);
cbn->set_index(index);
//当前buffer 链尾为空,说明当前链表为空
if (_completed_buffers_tail == NULL) {
//创建链表,本次申请的节点作为头结点和尾节点
_completed_buffers_head = cbn;
_completed_buffers_tail = cbn;
//当前链表不为空
} else {
//将新节点加入链尾
_completed_buffers_tail->set_next(cbn);
//尾指针移到新节点
_completed_buffers_tail = cbn;
}
//链表长度 +1
_n_completed_buffers++;
//判断是否达到阈值还有Refine工作,没有则通知
if (!_process_completed && _process_completed_threshold >= 0 &&
_n_completed_buffers >= _process_completed_threshold) {
_process_completed = true;
if (_notify_when_complete)
//通知0号线程启动
_cbl_mon->notify();
}
}
以上代码一大堆,就做了这点事:如下图:
- 添加引用对象到DCQ
- 添加DCQ到DCQS链尾
以上我们已经完成 : 引用记录->DCQ,DCQ->DCQS
那么Refine线程是怎么处理DCQ的呢?让我们继续来揭开它神秘的面纱:
- 我们知道Refine线程随着GC管理器初始化的时候初始化,
- 并发线程池中0 -(n-1)号线程都处于冻结状态,
- 由任意一个Mutator通过Monitor来notify 0号线程启动处理DCQ,
- 前一个Refine线程发现自己太忙则激活后一个,Refine线程发现自己太闲则主动冻结自己
- Mutator 发现DCQS太忙则主动帮助Refine线程处理DCQ
- G1设置在不同负载下启动不同数量的Refine线程
- 工作负载通过
RefinementZone
控制
Refinement Zone:
Refine线程最主要的工作就是更新RSet
RSet多数情况下在堆内存开销 1% ~ 20%, 即100G的空间RSet最多占20G
跟新RSet记录数太多,将DCQS占满,Mutator会主动帮助Refine线程处理DQC,会导致Mutator很慢
所以G1将DCQS分成4个Zone,不同区对应不同工作负载,启动不同数目的Refine线程
- 白区 - 默认,[0,green) 不处理
- 绿区 -
G1ConcRefinementGreenZone
[green,yellow) 部分Refine线程启动 - 黄区 -
G1ConcRefinementYellowZone
[yellow,red) 全部Refine线程启动 - 红区 -
G1ConcRefinementRedZone
[red,+无穷) 全部优化线程 + Mutator线程启动