GC悲观策略之Serial GC篇

仍然是 这篇blog:GC悲观策略之Parallel GC篇中的代码,换成-Xms30m -Xmx30m -Xmn10m -XX:+UseSerialGC后执行的结果为YGC、YGC、YGC、YGC、FGC。

原因就在于Serial GC的悲观策略是不同的,Serial GC在执行YGC时,首先进入如下代码片段进行检查:

void DefNewGeneration::collect(bool full,
bool clear_all_soft_refs,
size_t size,
bool is_tlab) {
…
if (!collection_attempt_is_safe()) {
gch->set_incremental_collection_will_fail();
return;
}
…
}
bool DefNewGeneration::collection_attempt_is_safe() {
if (!to()->is_empty()) {
return false;
}
if (_next_gen == NULL) {
GenCollectedHeap* gch = GenCollectedHeap::heap();
_next_gen = gch->next_gen(this);
assert(_next_gen != NULL,
"This must be the youngest gen, and not the only gen");
}

const double evacuation_ratio = MaxLiveObjectEvacuationRatio / 100.0;

size_t worst_case_evacuation = (size_t)(used() evacuation_ratio);
// 这里的_next_gen也就是旧生代了,下面贴出旧生代对应的代码
return _next_gen->promotion_attempt_is_safe(worst_case_evacuation,
HandlePromotionFailure);
}
bool TenuredGeneration::promotion_attempt_is_safe(
size_t max_promotion_in_bytes,
bool younger_handles_promotion_failure) const {
bool result = max_contiguous_available() >= max_promotion_in_bytes;
if (younger_handles_promotion_failure && !result) {
result = max_contiguous_available() >=
(size_t) gc_stats()->avg_promoted()->padded_average();
if (PrintGC && Verbose && result) {
gclog_or_tty->print_cr("TenuredGeneration::promotion_attempt_is_safe"
" contiguous_available: " SIZE_FORMAT
" avg_promoted: " SIZE_FORMAT,
max_contiguous_available(),
gc_stats()->avg_promoted()->padded_average());
}
} else {
if (PrintGC && Verbose) {
gclog_or_tty->print_cr("TenuredGeneration::promotion_attempt_is_safe"
" contiguous_available: " SIZE_FORMAT
" promotion_in_bytes: " SIZE_FORMAT,
max_contiguous_available(), max_promotion_in_bytes);
}
}
return result;
}
这个检查首先是检查目前新生代中使用的空间是否大于了旧生代剩余的空间,如大于且HandlePromotionFailure为true(默认值),那么再检查旧生代剩余的空间是否大于之前平均晋升的old的大小,如大于则返回true,小于则返回false,在返回false的情况下,就不进行YGC 的剩下的操作了。
按照这样的规则,在第九次循环的时候,应该执行的是FGC,而不是YGC,这里的原因是在Serial GC时,是先进行计数和时间的统计等,再调用DefNewGeneration的collect的,因此尽管这次没有真正的执行YGC的动作,但还是被计数和计入时间了,但这次为什么GC log中输出的不是Full GC呢,请看下面的代码片段:

void GenCollectedHeap::do_collection(bool full,
bool clear_all_soft_refs,
size_t size,
bool is_tlab,
int max_level) {
…
// 在当前场景下,传入的full为false,因此complete为false
bool complete = full && (max_level == (n_gens()-1));
const char gc_cause_str = "GC ";
if (complete) {
GCCause::Cause cause = gc_cause();
if (cause == GCCause::_java_lang_system_gc) {
gc_cause_str = "Full GC (System) ";
} else {
gc_cause_str = "Full GC ";
}
}
…
for (int i = starting_level; i <= max_level; i++) {
if (_gens[i]->should_collect(full, size, is_tlab)) {
…
// Determine if allocation request was met.
if (size > 0) {
if (!is_tlab || _gens[i]->supports_tlab_allocation()) {
if (size*HeapWordSize <= _gens[i]->unsafe_max_alloc_nogc()) {
size = 0;
}
}
}
…
}
}
从上可看出,当YGC结束后,eden的空间可以满足分配的需求的话,需要分配的对象的大小size就被置为零了,而在第九次循环中,由于YGC提前结束,因此eden的空间是仍然不足的,此时需要分配的size大小会不变,上面的GC动作还将进入TenuredGeneration的 should_allocate来进行检查了,该方法的代码片段如下:

bool TenuredGeneration::should_collect(bool full,
size_t size,
bool is_tlab) {
// This should be one big conditional or (||), but I want to be able to tell
// why it returns what it returns (without re-evaluating the conditionals
// in case they aren’t idempotent), so I’m doing it this way.
// DeMorgan says it’s okay.
bool result = false;
// 因为full是false,因此进入不了这里
if (!result && full) {
result = true;
if (PrintGC && Verbose) {
gclog_or_tty->print_cr("TenuredGeneration::should_collect: because"
" full");
}
}
if (!result && should_allocate(size, is_tlab)) {
result = true;
if (PrintGC && Verbose) {
gclog_or_tty->print_cr("TenuredGeneration::should_collect: because"
" should_allocate(" SIZE_FORMAT ")",
size);
}
}
// If we don’t have very much free space.
// XXX: 10000 should be a percentage of the capacity!!!
if (!result && free() < 10000) {
result = true;
if (PrintGC && Verbose) {
gclog_or_tty->print_cr("TenuredGeneration::should_collect: because"
" free(): " SIZE_FORMAT,
free());
}
}
// If we had to expand to accomodate promotions from younger generations
if (!result && _capacity_at_prologue < capacity()) {
result = true;
if (PrintGC && Verbose) {
gclog_or_tty->print_cr("TenuredGeneration::should_collect: because"
"_capacity_at_prologue: " SIZE_FORMAT " < capacity(): " SIZE_FORMAT,
_capacity_at_prologue, capacity());
}
}
return result;
}
virtual bool should_allocate(size_t word_size, bool is_tlab) {
bool result = false;
// 32 bit上BitsPerSize_t为32,64 bit上为64, LogHeapWordSize在32 bit为2,64 bit为3
size_t overflow_limit = (size_t)1 << (BitsPerSize_t - LogHeapWordSize);
if (!is_tlab || supports_tlab_allocation()) {
result = (word_size > 0) && (word_size < overflow_limit);
}
return result;
}
由此可看出,should_allocate为true,因此触发了FGC。

这样就可以理解为什么在第九次循环的时候打印出来的日志是没有Full GC字样的,但又计算为执行了一次YGC和一次FGC的。

由于Concurrent GC是基于Serial GC实现的,因此悲观策略是相同的。

ps: 如大家想研究这些东西,一方面是下载源码,另一方面也可以下载一个debug版本的jdk,这样就可以通过打开一些日志,看到更多的hotspot运行的细节,另外,也可以看出,Parallel GC的实现在代码上就清晰多了。

个人资料
bjchenli
等级:8
文章:260篇
访问:22.0w
排名: 3
上一篇: Makefile知识点整理
下一篇:GC悲观策略之Parallel GC篇
猜你感兴趣的圈子:
阿里中间件技术交流圈
标签: gc、bool、tlab、ygc、tenuredgeneration、面试题
隐藏