Panda白话 - G1垃圾收集器 之 Refine线程
先上一张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个分区、主动让出cpuif (regions_visited == 10) {if (sts.should_yield()) {sts.yield();// we just abandon the iterationbreak;}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,//全局MonitorDirtyCardQ_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 申请新的DCQhandle_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 fieldlocking_enqueue_completed_buffer(buf); // 加锁处理入队//buff不为空说明申请成功,返回if (_buf != NULL) return;} else {//不加锁申请DCQif (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) {//判断当前线程是否是用户线程-mutatorif (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) {//用户线程处理buffbool 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线程启动
Panda白话 - G1垃圾收集器 之 Refine线程相关推荐
- 转:深入理解Java G1垃圾收集器
本文首先简单介绍了垃圾收集的常见方式,然后再分析了G1收集器的收集原理,相比其他垃圾收集器的优势,最后给出了一些调优实践. 一,什么是垃圾回收 首先,在了解G1之前,我们需要清楚的知道,垃圾回收是什么 ...
- 深入理解 Java G1 垃圾收集器--转
原文地址:http://blog.jobbole.com/109170/?utm_source=hao.jobbole.com&utm_medium=relatedArticle 本文首先简单 ...
- 深入理解 Java G1 垃圾收集器
本文首先简单介绍了垃圾收集的常见方式,然后再分析了G1收集器的收集原理,相比其他垃圾收集器的优势,最后给出了一些调优实践. 一,什么是垃圾回收 首先,在了解G1之前,我们需要清楚的知道,垃圾回收是什么 ...
- G1 垃圾收集器原理详解
一.G1 垃圾收集器的开发背景: 1.CMS 垃圾收集器的缺陷: JVM 团队设计出 G1 收集器的目的就是取代 CMS 收集器,因为 CMS 收集器在很多场景下存在诸多问题,缺陷暴露无遗,具体如下: ...
- GC之G1垃圾收集器
GC之G1垃圾收集器 目录 以前收集器的特点 G1是什么 G1特点 G1底层原理 G1回收步骤 和CMS相比的优势 小总结 1. 以前收集器的特点 年轻代和老年代是各自独立且连续的内存块 年轻代收集必 ...
- JVM性能调优实践:G1 垃圾收集器介绍篇
前言 前面两篇主要整理了性能测试的主要观察指标信息:性能测试篇,以及JVM性能调优的工具:JVM篇.这一篇先简单总结一下GC的种类,然后侧重总结下G1(Garbage-First)垃圾收集器的分代,结 ...
- 深入理解 Java G1 垃圾收集器GC调优
本文首先简单介绍了垃圾收集的常见方式,然后再分析了G1收集器的收集原理,相比其他垃圾收集器的优势,最后给出了一些调优实践. 一,什么是垃圾回收 首先,在了解G1之前,我们需要清楚的知道,垃圾回收是什么 ...
- G1垃圾收集器设计目标与改良手段【纯理论】
在之前已经详细对CMS垃圾回收器进行了学习,今天准备要学习另一个全新的垃圾收集器---G1(Garbage First Collector 垃圾优先的收集器),说是一种全新的,其实G1垃圾收集器已经出 ...
- G1垃圾收集器深度剖析
G1垃圾收集器深度剖析 一.G1垃圾收集器概述 1.1 思考 开始学习前,抛出两个常见面试问题:1.G1的回收原理是什么?为什么G1比传统的GC回收性能好?2.为什么G1如此完美仍然会有ZGC?简单的 ...
最新文章
- 20 条非常实用的 Python 代码,建议收藏!
- Discuz!NT 模板机制分析(转)
- Sharepoint学习笔记—ECM系列--文档集(Document Set)的实现
- EventLoopGroup 与Reactor
- Spring中IoC的入门实例[转]
- python输入字符串str_python字符串(str)
- typescript索引类型_typescript入门:高级类型
- “人大女神”事件思考网络事件背后的教育文化
- 多线程解决同步问题浅析
- [Python3] 面向对象编程
- 对比学习Python实现
- VAX/VMS虚拟内存系统
- php ayyay,PHP: curl_setopt - Manual
- React/Vue/Nerv 任你选,多端框架 Taro 发布 3.0 RC 版本
- SAP License:SAP系统备料发货时的流程规范
- 最近在琢磨的一个问题和我的尝试
- 本地ASP.NET开发页面使用AzureAD(AAD)验证登录
- 图解IFRS9 金融工具(8)减值准备规则比较
- Android如何制作.9图片
- 怎么建立设备管理系统?
热门文章
- PHPexcel文档 laravelExecl可参考
- Word文档中X的平方怎么打出来?
- 蚂蚁金服智能推荐引擎解决方案与实践
- 网易云音乐-面临百万请求的最优方案(公开课-笔记)
- 冷却塔为什么设计成双曲线型?
- ps怎么创建双曲线图层如何添加
- new Date() 获取当前时间对象(getFullYear、getMonth、getDate、getHours、getMinutes、getSeconds、getDay、getTime)
- 钟茜的工作日志跟在这里
- spyder pyecharts不显示_后期剪辑还在升级主机配置?换台京东方显示器才最重要...
- mysql删除列命令_MySQL删除列