Parralel Scavenge 收集器工作流程
jvm初始化的時(shí)候耀找,有個(gè)重要的步驟是全局堆的初始化,根據(jù)vm參數(shù)的不同翔悠,又會(huì)選擇不同的堆實(shí)現(xiàn)(堆的實(shí)現(xiàn)在share/vm/memory中,策略選擇位于share/vm/gc_implementation, 詳情見Universe::initialize_heap()
)涯呻。server模式下啟動(dòng)的jvm,默認(rèn)采用的全局堆實(shí)現(xiàn)是ParallelScavengeHeap
,本文記錄了jdk1.8版本的Parralel Scavenge實(shí)現(xiàn),很多細(xì)節(jié)還不清楚凉驻。
一、PS收集器概覽及初始化
ParallelScavengeHeap由兩個(gè)區(qū)域組成复罐,_old_gen和_young_gen,其中_young_gen又由eden,from雄家,to三個(gè)MutableSpace
組成效诅。大致如下:
我們可以用:
- -xmn500m 來指定新時(shí)代的大小為500m
- -XX:SurvivorRatio=8 來指定eden區(qū)和(from+to)區(qū)占比8:1.
- -XX:MaxTenuringThreshold=18 來指定晉升年齡
新生代的3個(gè)區(qū)域是連續(xù)的空間,做法是開辟一個(gè)虛擬內(nèi)存,根據(jù)起始大小計(jì)算分配出eden趟济,兩個(gè)survivor區(qū)域乱投。虛擬內(nèi)存不會(huì)立馬占用物理內(nèi)存,每次分配對(duì)象時(shí)載入物理內(nèi)存顷编。在首次將新生代數(shù)據(jù)晉升到年老代時(shí)物理內(nèi)存會(huì)急劇升高,(測試:在指定xmn大小為500m的情況下戚炫,每次new 4m大小空間,約400m時(shí)觸發(fā)一次 minor gc媳纬,內(nèi)存突然增長到750m)双肤。
java的new關(guān)鍵字實(shí)際會(huì)作用到j(luò)vm的申請(qǐng)內(nèi)存操作(聲明的對(duì)象名稱會(huì)在棧內(nèi)分配),調(diào)用到interpreterRuntime.cpp中的InterpreterRuntime::_new方法钮惠,為了方便觸發(fā)gc茅糜,我用了new大數(shù)組的方式來調(diào)試。
創(chuàng)建數(shù)組的入口函數(shù)有兩個(gè)素挽,一個(gè)是用于基本類型的內(nèi)存申請(qǐng)蔑赘, 另一個(gè)是對(duì)象數(shù)組的。
IRT_ENTRY(void, InterpreterRuntime::newarray(JavaThread* thread, BasicType type, jint size))
oop obj = oopFactory::new_typeArray(type, size, CHECK);
thread->set_vm_result(obj);
IRT_END
IRT_ENTRY(void, InterpreterRuntime::anewarray(JavaThread* thread, ConstantPool* pool, int index, jint size))
// Note: no oopHandle for pool & klass needed since they are not used
// anymore after new_objArray() and no GC can happen before.
// (This may have to change if this code changes!)
Klass* klass = pool->klass_at(index, CHECK);
objArrayOop obj = oopFactory::new_objArray(klass, size, CHECK);
thread->set_vm_result(obj);
IRT_END
申請(qǐng)內(nèi)存需要經(jīng)過以下幾個(gè)步驟预明。
1.嘗試從tlab中分配內(nèi)存缩赛,分配成功直接返回;不成功進(jìn)入下一步撰糠。
2.從垃圾收集器中分配內(nèi)存酥馍,如果沒有相關(guān)指定收集器的配置,Server模式默認(rèn)的是parralel Scavenge的方式分配窗慎。
3.從新生代中分配物喷,關(guān)鍵代碼
HeapWord* result = young_gen()->allocate(size);
卤材,我們知道新生代實(shí)際直接存放新建對(duì)象的區(qū)域是eden區(qū),關(guān)鍵代碼HeapWord* result = eden_space()->cas_allocate(word_size);
4.內(nèi)存的實(shí)際分配是MutableSpace類實(shí)現(xiàn)的峦失,判斷當(dāng)前剩余空閑內(nèi)存是否足夠放下申請(qǐng)的對(duì)象扇丛,如果可以,那么成功返回尉辑。如果不行帆精,進(jìn)入步驟5。
5.如果對(duì)象的大小大于eden區(qū)的一半隧魄,那么直接分配到老年代中去卓练,否則進(jìn)入下一步。
HeapWord* ParallelScavengeHeap::mem_allocate_old_gen(size_t size) {
if (!should_alloc_in_eden(size) || GC_locker::is_active_and_needs_gc()) {
// Size is too big for eden, or gc is locked out.
return old_gen()->allocate(size);
}
// If a "death march" is in progress, allocate from the old gen a limited
// number of times before doing a GC.
if (_death_march_count > 0) {
if (_death_march_count < 64) {
++_death_march_count;
return old_gen()->allocate(size);
} else {
_death_march_count = 0;
}
}
return NULL;
}
inline bool ParallelScavengeHeap::should_alloc_in_eden(const size_t size) const
{
const size_t eden_size = young_gen()->eden_space()->capacity_in_words();
return size < eden_size / 2;
}
- 6.觸發(fā)一個(gè)
VM_ParallelGCFailedAllocation
任務(wù)拋給vm線程,這里注意下VMThread的execute方法是阻塞的购啄,需要等到vm線程的gc任務(wù)完成當(dāng)前線程才會(huì)返回襟企。默認(rèn)的任務(wù)是VM_ParallelGCFailedAllocation。查看其doit
方法
void VM_ParallelGCFailedAllocation::doit() {
SvcGCMarker sgcm(SvcGCMarker::MINOR);
ParallelScavengeHeap* heap = (ParallelScavengeHeap*)Universe::heap();
assert(heap->kind() == CollectedHeap::ParallelScavengeHeap, "must be a ParallelScavengeHeap");
GCCauseSetter gccs(heap, _gc_cause);
_result = heap->failed_mem_allocate(_word_size);
if (_result == NULL && GC_locker::is_active_and_needs_gc()) {
set_gc_locked();
}
}
HeapWord* ParallelScavengeHeap::failed_mem_allocate(size_t size) {
assert(SafepointSynchronize::is_at_safepoint(), "should be at safepoint");
assert(Thread::current() == (Thread*)VMThread::vm_thread(), "should be in vm thread");
assert(!Universe::heap()->is_gc_active(), "not reentrant");
assert(!Heap_lock->owned_by_self(), "this thread should not own the Heap_lock");
// We assume that allocation in eden will fail unless we collect.
// First level allocation failure, scavenge and allocate in young gen.
GCCauseSetter gccs(this, GCCause::_allocation_failure);
const bool invoked_full_gc = PSScavenge::invoke();
HeapWord* result = young_gen()->allocate(size);
// Second level allocation failure.
// Mark sweep and allocate in young generation.
if (result == NULL && !invoked_full_gc) {
do_full_collection(false);
result = young_gen()->allocate(size);
}
death_march_check(result, size);
// Third level allocation failure.
// After mark sweep and young generation allocation failure,
// allocate in old generation.
if (result == NULL) {
result = old_gen()->allocate(size);
}
// Fourth level allocation failure. We're running out of memory.
// More complete mark sweep and allocate in young generation.
if (result == NULL) {
do_full_collection(true);
result = young_gen()->allocate(size);
}
// Fifth level allocation failure.
// After more complete mark sweep, allocate in old generation.
if (result == NULL) {
result = old_gen()->allocate(size);
}
return result;
}
- 7.可以看到狮含,先執(zhí)行一次minor gc顽悼,然后嘗試在新生代分配。如果不成功且minor gc執(zhí)行過程中沒有去full gc几迄,那么要來一次fullgc,嘗試從新生代中分配蔚龙。 還不成功,再來一次fullgc(帶清楚軟引用的),從新生代中分配映胁。依然不成功木羹,從老年代分配。還不成功解孙?OOM吧坑填。。
重點(diǎn)是const bool invoked_full_gc = PSScavenge::invoke();
貼一下實(shí)現(xiàn),先執(zhí)行一次minor gc妆距。不成功的話穷遂,觸發(fā)fullgc。fullgc根據(jù)配置有兩種選擇娱据,PSMarkSweep和PSParallel蚪黑。沒有CMS,因?yàn)镻arraler Scavenge收集器和他無法同時(shí)使用中剩。
bool PSScavenge::invoke() {
assert(SafepointSynchronize::is_at_safepoint(), "should be at safepoint");
assert(Thread::current() == (Thread*)VMThread::vm_thread(), "should be in vm thread");
assert(!Universe::heap()->is_gc_active(), "not reentrant");
ParallelScavengeHeap* const heap = (ParallelScavengeHeap*)Universe::heap();
assert(heap->kind() == CollectedHeap::ParallelScavengeHeap, "Sanity");
PSAdaptiveSizePolicy* policy = heap->size_policy();
IsGCActiveMark mark;
const bool scavenge_done = PSScavenge::invoke_no_policy();
const bool need_full_gc = !scavenge_done ||
policy->should_full_GC(heap->old_gen()->free_in_bytes());
bool full_gc_done = false;
if (UsePerfData) {
PSGCAdaptivePolicyCounters* const counters = heap->gc_policy_counters();
const int ffs_val = need_full_gc ? full_follows_scavenge : not_skipped;
counters->update_full_follows_scavenge(ffs_val);
}
if (need_full_gc) {
GCCauseSetter gccs(heap, GCCause::_adaptive_size_policy);
CollectorPolicy* cp = heap->collector_policy();
const bool clear_all_softrefs = cp->should_clear_all_soft_refs();
if (UseParallelOldGC) {
full_gc_done = PSParallelCompact::invoke_no_policy(clear_all_softrefs);
} else {
full_gc_done = PSMarkSweep::invoke_no_policy(clear_all_softrefs);
}
}
return full_gc_done;
}
接下來進(jìn)入重點(diǎn)了忌穿,minor gc的具體實(shí)現(xiàn)在PSScavenge::invoke_no_policy
,代碼太長不貼了结啼。簡單說下流程掠剑。
1.sanity check,記錄gc前的內(nèi)存信息郊愧。
2.向gc worker線程池投遞gc任務(wù)朴译,根據(jù)根節(jié)點(diǎn)搜索法保存有效的對(duì)象井佑。根節(jié)點(diǎn)有很多種,如下。注意這些gc任務(wù)是并發(fā)執(zhí)行的眠寿。
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::universe));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::jni_handles));
// We scan the thread roots in parallel
Threads::create_thread_roots_tasks(q);
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::object_synchronizer));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::flat_profiler));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::management));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::system_dictionary));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::class_loader_data));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::jvmti));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::code_cache));
可達(dá)性分析算法實(shí)現(xiàn)都差不多躬翁,步驟是遍歷根節(jié)點(diǎn)的所有可達(dá)對(duì)象,判斷對(duì)象是不是在新生代(根據(jù)內(nèi)存地址判斷)盯拱,是的話調(diào)用PSPromotionManager::copy_to_survivor_space
轉(zhuǎn)移存活對(duì)象盒发,如果指定了需要晉升或者對(duì)象的年齡(新生代對(duì)象每經(jīng)過一次minor gc加1歲)達(dá)到闕值,則拷貝到old區(qū)狡逢,否則拷貝到to區(qū)宁舰。
3.清空eden區(qū)和from區(qū),再將有存活的對(duì)象的to區(qū)和from區(qū)做交換(保證存活對(duì)象放在from區(qū)奢浑,這樣下次gc的時(shí)候就可以再次執(zhí)行復(fù)制算法將對(duì)象拷貝到to區(qū)了)蛮艰。
- 調(diào)用
resize_young_gen
方法重新分配新時(shí)代大小(疑問:看注釋是minor gc會(huì)引起新生代大小的變化,具體什么情況殷费?)
- 調(diào)用
4.如果有對(duì)象晉升失敗了印荔,那么可能是old區(qū)空間不足了,此時(shí)需要觸發(fā)一次full gc,如果老年代的策略是ps old,那么處理老年代的gc由類
PSParallelCompact
,這一段后續(xù)分析详羡。