先上一张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满 则 申请新的DCQhandle_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线程相关推荐

  1. 转:深入理解Java G1垃圾收集器

    本文首先简单介绍了垃圾收集的常见方式,然后再分析了G1收集器的收集原理,相比其他垃圾收集器的优势,最后给出了一些调优实践. 一,什么是垃圾回收 首先,在了解G1之前,我们需要清楚的知道,垃圾回收是什么 ...

  2. 深入理解 Java G1 垃圾收集器--转

    原文地址:http://blog.jobbole.com/109170/?utm_source=hao.jobbole.com&utm_medium=relatedArticle 本文首先简单 ...

  3. 深入理解 Java G1 垃圾收集器

    本文首先简单介绍了垃圾收集的常见方式,然后再分析了G1收集器的收集原理,相比其他垃圾收集器的优势,最后给出了一些调优实践. 一,什么是垃圾回收 首先,在了解G1之前,我们需要清楚的知道,垃圾回收是什么 ...

  4. G1 垃圾收集器原理详解

    一.G1 垃圾收集器的开发背景: 1.CMS 垃圾收集器的缺陷: JVM 团队设计出 G1 收集器的目的就是取代 CMS 收集器,因为 CMS 收集器在很多场景下存在诸多问题,缺陷暴露无遗,具体如下: ...

  5. GC之G1垃圾收集器

    GC之G1垃圾收集器 目录 以前收集器的特点 G1是什么 G1特点 G1底层原理 G1回收步骤 和CMS相比的优势 小总结 1. 以前收集器的特点 年轻代和老年代是各自独立且连续的内存块 年轻代收集必 ...

  6. JVM性能调优实践:G1 垃圾收集器介绍篇

    前言 前面两篇主要整理了性能测试的主要观察指标信息:性能测试篇,以及JVM性能调优的工具:JVM篇.这一篇先简单总结一下GC的种类,然后侧重总结下G1(Garbage-First)垃圾收集器的分代,结 ...

  7. 深入理解 Java G1 垃圾收集器GC调优

    本文首先简单介绍了垃圾收集的常见方式,然后再分析了G1收集器的收集原理,相比其他垃圾收集器的优势,最后给出了一些调优实践. 一,什么是垃圾回收 首先,在了解G1之前,我们需要清楚的知道,垃圾回收是什么 ...

  8. G1垃圾收集器设计目标与改良手段【纯理论】

    在之前已经详细对CMS垃圾回收器进行了学习,今天准备要学习另一个全新的垃圾收集器---G1(Garbage First Collector 垃圾优先的收集器),说是一种全新的,其实G1垃圾收集器已经出 ...

  9. G1垃圾收集器深度剖析

    G1垃圾收集器深度剖析 一.G1垃圾收集器概述 1.1 思考 开始学习前,抛出两个常见面试问题:1.G1的回收原理是什么?为什么G1比传统的GC回收性能好?2.为什么G1如此完美仍然会有ZGC?简单的 ...

最新文章

  1. 20 条非常实用的 Python 代码,建议收藏!
  2. Discuz!NT 模板机制分析(转)
  3. Sharepoint学习笔记—ECM系列--文档集(Document Set)的实现
  4. EventLoopGroup 与Reactor
  5. Spring中IoC的入门实例[转]
  6. python输入字符串str_python字符串(str)
  7. typescript索引类型_typescript入门:高级类型
  8. “人大女神”事件思考网络事件背后的教育文化
  9. 多线程解决同步问题浅析
  10. [Python3] 面向对象编程
  11. 对比学习Python实现
  12. VAX/VMS虚拟内存系统
  13. php ayyay,PHP: curl_setopt - Manual
  14. React/Vue/Nerv 任你选,多端框架 Taro 发布 3.0 RC 版本
  15. SAP License:SAP系统备料发货时的流程规范
  16. 最近在琢磨的一个问题和我的尝试
  17. 本地ASP.NET开发页面使用AzureAD(AAD)验证登录
  18. 图解IFRS9 金融工具(8)减值准备规则比较
  19. Android如何制作.9图片
  20. 怎么建立设备管理系统?

热门文章

  1. PHPexcel文档 laravelExecl可参考
  2. Word文档中X的平方怎么打出来?
  3. 蚂蚁金服智能推荐引擎解决方案与实践
  4. 网易云音乐-面临百万请求的最优方案(公开课-笔记)
  5. 冷却塔为什么设计成双曲线型?
  6. ps怎么创建双曲线图层如何添加
  7. new Date() 获取当前时间对象(getFullYear、getMonth、getDate、getHours、getMinutes、getSeconds、getDay、getTime)
  8. 钟茜的工作日志跟在这里
  9. spyder pyecharts不显示_后期剪辑还在升级主机配置?换台京东方显示器才最重要...
  10. mysql删除列命令_MySQL删除列