Skip to content

g1 other When_sec

ceragon edited this page Dec 27, 2021 · 4 revisions

G1 的暂停开始时间控制

G1 关于暂停时间有一个原则,假设一个单位时间 X,在任意 X 时间内暂停总时间不会超过最大暂停时间。而实现这个原则的核心代码就是这个 when_sec(),用于计算下一次暂停的开始时间。

相关代码

代码解析
double G1MMUTrackerQueue::when_sec(double current_time, double pause_time) {
    // 先说下两个参数,分别是当前的时间(单位s)和参数 MaxGCPauseMillis 的秒值(根据上面的方法 max_gc_time() 得知)
    // adjusted_pause_time 可以理解为预计的暂停时间
  double adjusted_pause_time = (pause_time > max_gc_time()) ? max_gc_time() : pause_time;
  // 可以理解为如果立即开始暂停,最早什么时候暂停结束
  double earliest_end = current_time + adjusted_pause_time;
  // _time_slice 是参数 GCPauseIntervalMillis 的秒值,默认值是 201ms == 0.201s
  // limit 可以理解为单位时间的起始时间
  double limit = earliest_end - _time_slice;
  // 计算一下某个时间范围内的暂停时间总和, 时间范围是 (earliest_end - _time_slice) ~ earliest_end
  double gc_time = calculate_gc_time(earliest_end);
  // 先分析 (gc_time + adjusted_pause_time), 在某个一时间段内总的暂停时间,时间段范围看上面一行代码
  // 再分析 (- max_gc_time()),如果 diff 为负,表示这段时间内的暂停时间超过了最大暂停时间
  // 如果 diff 为正,则这段时间内的总暂停时间没有超过最大值
  double diff = gc_time + adjusted_pause_time - max_gc_time();
  if (is_double_leq_0(diff))
      // 现在就开始暂停,不会违背 G1 关于任意单位时间点暂停时间不会超出最大暂停时间的原则
    return 0.0;
  // 走到这里时,diff 的含义就是超过最大暂停时间的秒数
    // 队尾索引
  int index = _tail_index;
  while ( 1 ) {
      // 第一次会取出队尾元素
    G1MMUTrackerQueueElem *elem = &_array[index];
    // limit = 当前时间 + 预期的暂停时间(X) - 暂停时间间隔(Y). 
    // 可以这么解读:如果现在开始暂停的话,需要持续暂停 X 秒.那么在暂停结束之前的 Y 秒内,总的暂停时间不能超过最大暂停时间.
    // elem 表示某一次的暂停
    // 下面的代码表示这次暂停时间是否在考察时间范围内
    if (elem->end_time() > limit) {
        // 先理解一下 diff 的含义,它是超出最大暂停时间的偏移量.
        // 也就是说现在就开始执行暂停的话,单位时间内的总暂停时间会超标.
        // 那直接延迟 diff 秒就开始暂停行不行呢?
        // 答案是不行,理由是
      if (elem->start_time() > limit)
        diff -= elem->duration();
      else
        diff -= elem->end_time() - limit;
      
      if (is_double_leq_0(diff)){
          // 首先能进到这里,说明 diff 为负了
          // 下面的代码有点复杂,但是先记住一点,当前这个方法会返回"下一次暂停的起始时间"
          // 我使用交换律对下面的代码做下调整,调整如下:
          // (end_time() + _time_slice + diff) - (adjusted_pause_time + current_time)
          // () - (如果当前开始暂停,什么时候结束)
          return  elem->end_time() + diff + _time_slice - adjusted_pause_time - current_time;
      }
          
    }
    // 根据下面的对于 _array 的解析,它是从 head 入队,tail 出队,
    // 所以用 tail + 1 的方式可以一直遍历到 head
    index = trim_index(index+1);
  }
}

时序图

相关方法或成员变量解析

max_gc_time() 与 _time_slice

  • max_gc_time() 是最大暂停秒数
  • _time_slice 是暂停时间的间隔(也可以视为单位时间片)
解析

max_gc_time() 方法

double max_gc_time() const {
    return _max_gc_time;
}

赋值过程

过程
  1. 以下是对 _max_gc_time 和 _time_slice 进行赋值的唯一方法
G1MMUTracker::G1MMUTracker(double time_slice, double max_gc_time) :
  _time_slice(time_slice),
  _max_gc_time(max_gc_time) { 
    // 在本构造函数中对两个参数进行了赋值
}

G1MMUTrackerQueue::G1MMUTrackerQueue(double time_slice, double max_gc_time) :
  G1MMUTracker(time_slice, max_gc_time),
  _head_index(0),
  _tail_index(trim_index(_head_index+1)),
  _no_entries(0) { 
    // 这个构造函数调用了调用了上面的构造函数
}
  1. 查看构造函数的调用,其中 GCPauseIntervalMillis 的默认值是 201,MaxGCPauseMillis 的默认值是 200
G1Policy::G1Policy(STWGCTimer* gc_timer) :
        _mmu_tracker(new G1MMUTrackerQueue(GCPauseIntervalMillis / 1000.0, MaxGCPauseMillis / 1000.0)){
    // 可以看到对 _time_slice 赋值的是 GCPauseIntervalMillis / 1000.0
    // 对 max_gc_time 进行赋值的是 MaxGCPauseMillis / 1000.0
}

calculate_gc_time()

记录某个时间范围的暂停时间总和。时间范围是 (current_time - GCPauseIntervalMillis) ~ (current_time)

解析

方法内容

double G1MMUTrackerQueue::calculate_gc_time(double current_time) {
    // 记录一下在过去的 0.201s 内,暂停时间的总和
   double gc_time = 0.0;
   // 时间限制 = 当前时间 - 0.201s
   double limit = current_time - _time_slice;
   // _no_entries 是有效元素个数
   for (int i = 0; i < _no_entries; ++i) {
       // 获取队尾的索引
      int index = trim_index(_tail_index + i);
      // 取出队尾元素
      G1MMUTrackerQueueElem *elem = &_array[index];
      // 结果为 false 时: 说明这次的暂停在过去的 0.201s 之前
      // 结果为 true 时:说在这次暂停发生在过去的 0.201s 之内
      if (elem->end_time() > limit) {
          // false: 表示这次暂停的起始时间在 过去的 0.201s 之前
          // true: 表示这次暂停的起始时间在 过去的 0.201s 之内
         if (elem->start_time() > limit)
             // 添加这次暂停的时间
            gc_time += elem->duration();
         else
             // 添加在 0.201s 内的时间
            gc_time += elem->end_time() - limit;
      }
   }
   return gc_time;
}

相关方法或成员变量解析

trim_index()

对传入参数与 QueueLength 进行取模运算

解析
相关代码
inline int trim_index(int index) {
   // 不太理解为啥要加 QueueLength
   // 对 index 进行取模运算
   return (index + QueueLength) % QueueLength;
}
_no_entries 和 _array 变量
  • _no_entries 用于记录 _array 中有效元素的个数
  • _array 一个循环列表,队首入队,队尾出队。记录了最近几次 Pause 的起始和结束时间
解析
赋值代码

有效赋值为两处,但其实都是 add_pause() 这个方法

void G1MMUTrackerQueue::add_pause(double start, double end) {
    // 先说下参数,分别是暂停的开始时间和结束时间
    // 移除过期的元素
    remove_expired_entries(end);
    // QueueLength 的值是 64
    if (_no_entries == QueueLength) {
        // 这两行代码变相的移出了队尾的元素
        _head_index = trim_index(_head_index + 1);
        _tail_index = trim_index(_tail_index + 1);
    } else {
        _head_index = trim_index(_head_index + 1);
        // 只要不超过队列长度就自增
        ++_no_entries;
    }
    // 通过上面的代码基本明白了 _array 是一个循环列表
    // 而且是队首插入新元素
    _array[_head_index] = G1MMUTrackerQueueElem(start, end);
}
void G1MMUTrackerQueue::remove_expired_entries(double current_time) {
    // 时间点 = (当前时间) - (GC 暂停间隔(默认是 0.201s))
    double limit = current_time - _time_slice;
    while (_no_entries > 0) {
        // _array[_tail_index].end_time() < limit 时返回 true
        if (is_double_geq(limit, _array[_tail_index].end_time())) {
            // 满足条件后,就会把队尾元素移出队列
            _tail_index = trim_index(_tail_index + 1);
            // 因为有元素被移出,所以有效元素数量减少
            --_no_entries;
        } else {
            return;
        }
    }
}
is_double_geq() 解析

需要传入两个参数,A 和 B。方法是 (B - A) < 0.0000001

解析
#define SMALL_MARGIN 0.0000001
#define is_double_leq_0(_value) ( (_value) < SMALL_MARGIN )
#define is_double_geq(_val1, _val2) is_double_leq_0((_val2) - (_val1))

展开宏之后的代码是

bool is_double_geq(double _val1, double _val2) {
    return (_val2 - _val1) < 0.0000001
}
赋值代码的调用链
调用链
  1. 调用 add_pause() 的位置如下:
void G1Policy::record_pause(PauseKind kind, double start, double end) {
    if (kind != FullGC) {
        _mmu_tracker->add_pause(start, end);
    }
}
  1. 调用上面 record_pause() 的共有四处,分别是
    • 每次 Young GC 或者 Mixed GC 后调用
    • 并发标记的最后一个阶段 cleanup 执行完成后调用
    • 并发标记的 remark 阶段执行结束后调用
    • Full GC 执行结束后调用
void G1Policy::record_collection_pause_end(double pause_time_ms, size_t cards_scanned, size_t heap_used_bytes_before_gc) {
    double end_time_sec = os::elapsedTime();
    record_pause(young_gc_pause_kind(), end_time_sec - pause_time_ms / 1000.0, end_time_sec);
}
void G1Policy::record_concurrent_mark_cleanup_end() {
    double end_sec = os::elapsedTime();
    record_pause(Cleanup, _mark_cleanup_start_sec, end_sec);
}
void G1Policy::record_concurrent_mark_remark_end() {
    double end_time_sec = os::elapsedTime();
    record_pause(Remark, _mark_remark_start_sec, end_time_sec);
}
void G1Policy::record_full_collection_end() {
    double end_sec = os::elapsedTime();
    record_pause(FullGC, _full_collection_start_sec, end_sec);
}

Clone this wiki locally