GC悲观策略之Parallel GC篇

先来看段代码:java

[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[1024*1024*3]);
}
caches.clear();
for(int i=0;i<2;i++){
caches.add(new byte[1024*1024*3]);
}
}
}
[/java]

当用-Xms30m-Xmx30m-Xmn10m-XX:+UseParallelGC执行上面的代码时会执行几回MinorGC和几回FullGC呢?
按照eden空间不足时触发minorgc的规则,上面代码执行后的GC应为:M、M、M、M,但实际上上面代码执行后GC则为:M、M、M、F、F。
这里的缘由就在于ParallelScavengeGC时的悲观策略,当在eden上分配内存失败时且对象的大小尚不须要直接在old上分配时,会触发YGC,代码片断以下:ide

[java]
void PSScavenge::invoke(){
…
bool scavenge_was_done = PSScavenge::invoke_no_policy();
PSGCAdaptivePolicyCounters* counters = heap->gc_policy_counters();
if (UsePerfData)
counters->update_full_follows_scavenge(0);
if (!scavenge_was_done ||
policy->should_full_GC(heap->old_gen()->free_in_bytes())) {
if (UsePerfData)
counters->update_full_follows_scavenge(full_follows_scavenge);
GCCauseSetter gccs(heap, GCCause::_adaptive_size_policy);
if (UseParallelOldGC) {
PSParallelCompact::invoke_no_policy(false);
} else {
PSMarkSweep::invoke_no_policy(false);
}
}
…
}
PSScavenge::invoke_no_policy{
…
if (!should_attempt_scavenge()) {
return false;
}
…
}
bool PSScavenge::should_attempt_scavenge() {
…
PSAdaptiveSizePolicy* policy = heap->size_policy();
size_t avg_promoted = (size_t) policy->padded_average_promoted_in_bytes();
size_t promotion_estimate = MIN2(avg_promoted, young_gen->used_in_bytes());
bool result = promotion_estimate < old_gen->free_in_bytes();
…
return result;
}
[/java]

在上面should_attempt_scavenge代码片断中,能够看到会比较以前YGC晋升到Old中的平均大小与当前新生代中已被使用的字节数大小,取更小的值与旧生代目前剩余空间大小对比,如更大,则返回false,就终止了YGC的执行了,当返回false时,PSScavenge::invoke就将触发FullGC了。
在PSScavenge:invoke中还有一个条件为:policy->should_full_GC(heap->old_gen()->free_in_bytes(),来看看这段代码片断:对象

[java]
bool PSAdaptiveSizePolicy::should_full_GC(size_t old_free_in_bytes) {
bool result = padded_average_promoted_in_bytes() > (float) old_free_in_bytes;
…
return result;
}
[/java]

可看到,这段代码检查的也是以前YGC时晋升到old的平均大小是否大于了旧生代的剩余空间,如大于,则触发fullgc。
总结上面分析的策略,能够看到采用ParallelGC的状况下,当YGC触发时,会有两个检查:
一、在YGC执行前,min(目前新生代已使用的大小,以前平均晋升到old的大小中的较小值)>旧生代剩余空间大小?不执行YGC,直接执行FullGC:执行YGC;
二、在YGC执行后,平均晋升到old的大小>旧生代剩余空间大小?触发FullGC:什么都不作。内存

按照这样的说明,再来看看上面代码的执行过程当中eden和old大小的变化情况:
代码edenoldYGCFGC
第一次循环3000
第二次循环6000
第三次循环3610
第四次循环6610
第五次循环31220
第六次循环61220
第七次循环31831
第八次循环61831
第九次循环3332
在第7次循环时,YGC后旧生代剩余空间为2m,而以前平均晋级到old的对象大小为6m,所以在YGC后会触发一次FGC。
而第9次循环时,在YGC执行前,此时新生代已使用的大小为6m,以前晋级到old的平均大小为6m,这二者去最小值为6m,这个值已大于old的剩余空间,所以就不执行YGC,直接执行FGC了。it

SunJDK之因此要有悲观策略,我猜测理由是程序最终是会以一个较为稳态的情况执行的,此时每次YGC后晋升到old的对象大小应该是差很少的,在YGC时作好检查,避免等YGC后晋升到Old的对象致使old空间不足,所以还不如干脆就直接执行FGC,正由于悲观策略的存在,你们有些时候可能会看到old空间没满但fullgc执行的情况。
埋个伏笔,你们将上面的执行参数换为-XX:+UseSerialGC执行看看,会发生什么呢?:)io

相关文章
相关标签/搜索