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

相關文章
相關標籤/搜索