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个分区、主动让出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满 则 申请新的DCQhandle_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线程启动
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值