Repository navigation
g1 other When_sec
ceragon edited this page Dec 27, 2021
·
4 revisions
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 是暂停时间的间隔(也可以视为单位时间片)
解析
double max_gc_time() const {
return _max_gc_time;
}过程
- 以下是对 _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) {
// 这个构造函数调用了调用了上面的构造函数
}- 查看构造函数的调用,其中
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
}记录某个时间范围的暂停时间总和。时间范围是 (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;
}对传入参数与 QueueLength 进行取模运算
解析
inline int trim_index(int index) {
// 不太理解为啥要加 QueueLength
// 对 index 进行取模运算
return (index + QueueLength) % QueueLength;
}- _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;
}
}
}需要传入两个参数,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
}调用链
- 调用
add_pause()的位置如下:
void G1Policy::record_pause(PauseKind kind, double start, double end) {
if (kind != FullGC) {
_mmu_tracker->add_pause(start, end);
}
}- 调用上面
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);
}