GC悲观策略之Serial GC篇

仍然是 这篇blog:GC悲观策略之Parallel GC篇中的代码,换成-Xms30m -Xmx30m -Xmn10m -XX:+UseSerialGC后执行的结果为YGC、YGC、YGC、YGC、FGC。
原因就在于Serial GC的悲观策略是不同的,Serial GC在执行YGC时,首先进入如下代码片段进行检查:
[c]
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;
}
[/c]
这个检查首先是检查目前新生代中使用的空间是否大于了旧生代剩余的空间,如大于且HandlePromotionFailure为true(默认值),那么再检查旧生代剩余的空间是否大于之前平均晋升的old的大小,如大于则返回true,小于则返回false,在返回false的情况下,就不进行YGC 的剩下的操作了。
按照这样的规则,在第九次循环的时候,应该执行的是FGC,而不是YGC,这里的原因是在Serial GC时,是先进行计数和时间的统计等,再调用DefNewGeneration的collect的,因此尽管这次没有真正的执行YGC的动作,但还是被计数和计入时间了,但这次为什么GC log中输出的不是Full GC呢,请看下面的代码片段:
[c]
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;
}
}
}

}
}
[/c]
从上可看出,当YGC结束后,eden的空间可以满足分配的需求的话,需要分配的对象的大小size就被置为零了,而在第九次循环中,由于YGC提前结束,因此eden的空间是仍然不足的,此时需要分配的size大小会不变,上面的GC动作还将进入TenuredGeneration的 should_allocate来进行检查了,该方法的代码片段如下:
[c]
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;
}
[/c]
由此可看出,should_allocate为true,因此触发了FGC。

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

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

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

本文来源于"阿里中间件团队播客",原文发表于"2010-11-08"

时间: 2024-09-28 15:20:37

GC悲观策略之Serial GC篇的相关文章

GC悲观策略之Parallel GC篇

先来看段代码: [java] import java.util.; public class SummaryCase{ public static void main(String[] args) throws Exception{ List caches=new ArrayList(); for(int i=0;i<7;i++){ caches.add(new byte[102410243]); } caches.clear(); for(int i=0;i<2;i++){ caches.a

HBase GC的前生今世 – 身世篇

在之前的HBase BlockCache系列文章中已经简单提到:使用LRUBlockCache缓存机制会因为CMS GC策略导致内存碎片过多,从而可能引发臭名昭著的Full GC,触发可怕的'stop-the-world'暂停,严重影响上层业务:而Bucket Cache缓存机制因为在初始化的时候就申请了一片固定大小的内存作为缓存,缓存淘汰不再由 JVM管理,数据Block的缓存操作只是对这篇空间的访问和覆盖,因而大大减少了内存碎片的出现,降低了Full GC发生的频率.那CMS GC策略如何导

HBase GC的前生今世 – 演进篇

最原始的HBase CMS GC相当严重,经常会因为碎片过多导致Promotion Failure,严重影响业务的读写请求.幸运的是,HBase并没有止步不前,很多优化方案相继被提出并贡献给社区,本文要介绍的就是几个比较重要的核心优化,分别是针对Memstore所作的两个优化:Thread-Local Allocation Buffer和MemStore Chunk Pool 以及针对BlockCache所作的优化:BucketCache方案.在详细介绍这几个优化之前有必要简单介绍一下HBase

浅谈建站策略:域名空间篇

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 一.域名篇 1.分开多个复合地关键字,如 www.search-engine-downzj.com 而不是 www.searchengine downzj.com,这样会使到搜索引擎更容易地正确理解你地关键字; 2.要记住域名地字符数不能超过55字,因为有些搜索引擎会对此有限制; 3.在一些目录里,如yahoo,dmoz中,编缉员不会在你页面

JVM 分代GC策略分析

我们以Sun HotSpot VM来进行分析,首先应该知道,如果我们没有指定任何GC策略的时候,JVM默认使用的GC策略.Java虚拟机是按照分代的方式来回收垃圾空间,我们应该知道,垃圾回收主要是针对堆(Heap)内存进行分代回收,将对内存可以分成新生代(Young Generation).年老代(Tenured Generation)和永久代(Permanent Generation)三个部分. 分代GC 分代GC包括如下三代: 新生代(Young Generation) 新生代有划分为Ede

jvm系列(十):如何优化Java GC「译」

本文由CrowHawk翻译,地址:如何优化Java GC「译」,是Java GC调优的经典佳作. Sangmin Lee发表在Cubrid上的"Become a Java GC Expert"系列文章的第三篇<How to Tune Java Garbage Collection>,本文的作者是韩国人,写在JDK 1.8发布之前,虽然有些地方有些许过时,但整体内容还是非常有价值的.译者此前也看到有人翻译了本文,发现其中有许多错漏生硬和语焉不详之处,因此决定自己翻译一份,供大

[JVM]成为JavaGC专家(1)—深入浅出Java垃圾回收机制

对于Java开发人员来说,了解垃圾回收机制(GC)有哪些好处呢?首先可以满足作为一名软件工程师的求知欲,其次,深入了解GC如何工作可以帮你写出更好的Java应用. 这仅仅代表我个人的意见,但我坚信一个精通GC的人往往是一个好的Java开发者.如果你对GC的处理过程感兴趣,说明你已经具备较大规模应用的开发经验.如果你曾经想过如何正确的选择GC算法,那意味着你已经完全理解你所开发的应用的特点.当然,我们不能以偏概全,这不能作为评价一个好的开发人员的共通标准.但是,我要说的是,深入理解GC是成为一名伟

一个性能较好的JVM参数配置

一个性能较好的web服务器jvm参数配置: -server//服务器模式 -Xmx2g //JVM最大允许分配的堆内存,按需分配 -Xms2g //JVM初始分配的堆内存,一般和Xmx配置成一样以避免每次gc后JVM重新分配内存. -Xmn256m //年轻代内存大小,整个JVM内存=年轻代 + 年老代 + 持久代 -XX:PermSize=128m //持久代内存大小 -Xss256k //设置每个线程的堆栈大小 -XX:+DisableExplicitGC //忽略手动调用GC, Syste

触发JVM进行Full GC的情况及应对策略

堆内存划分为 Eden.Survivor 和 Tenured/Old 空间,如下图所示: 从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC,对老年代GC称为Major GC,而Full GC是对整个堆来说的,在最近几个版本的JDK里默认包括了对永生带即方法区的回收(JDK8中无永生带了),出现Full GC的时候经常伴随至少一次的Minor GC,但非绝对的.Major GC的速度一般会比Minor GC慢10倍以上.下边看看有那种情况触发JVM进行Fu