java的一大核心特性,即是自動記憶體回收。這讓一些人從繁瑣的記憶體管理中解脫出來,但對大部分人來說,貌似這太理所當然了。因為現在市場上的語言,幾乎都已經沒有了還需要自己去管理記憶體這事。大家似乎都以為,語言不就應該幹這事嗎。
其實在我們現在的程式語言中,從某種角度上,大致可以分為多程式併發模型和多執行緒併發模型。程式的資源可以作業系統直接管理,而執行緒則依附於程式而存在。所以,從這個角度來說,也許多程式併發模型的記憶體回收,也許會簡單些。因為,多程式執行,可能帶來的就是程式的快速建立與消亡。而程式消亡後,就可以由作業系統進行管理記憶體了。這種程式語言多見於各種指令碼語言。但實際上,並沒有那麼簡單。雖說多執行緒併發模型中,必然伴隨著程式的長期執行,執行緒的反覆建立與銷燬,所以記憶體資源需要語言自行實現。但,多程式併發模型,難道就不可以長時間執行了嗎?難道就不會使用超過最大記憶體限制的空間了嗎?難道就沒有需要刪除的空間了嗎?其實它同樣都會面臨這些問題,所以,它們都會有記憶體回收的需求。只是各自實現的精細度不一樣,也是合理的。
網上有許多的資料,講解java gc的工作過程,引數控制,最佳實踐等等。但多半隻算得黑盒測試,但也足夠我們從容應對工作了。業有餘力之時,我們可以來看看,具體的gc如何用程式碼敲出來,亦是快事。
1. 垃圾回收演算法及收集器簡述
前面說了,有太多的文章講解這類工作。我們只提隻言片語。
如何判定一個物件是否可以回收,主要演算法有: 引用計數器演算法(簡單加減引用)、可達性分析演算法(圖論分析)。
而具體的記憶體回收,則可以分幾大演算法進行描述:標記-清除演算法,複製演算法,標記-整理演算法,混合之。具體處理過程,可以望文生義,也可以查詳細資料理解。
具體的java垃圾回收器:Serial/Serial Old收集器;ParNew收集器;Parallel Scavenge收集器;Parallel Old收集器;CMS(Concurrent Mark Sweep)收集器;G1收集器;ZGC收集器;具體解釋請參考其他資料。
2. hotspot垃圾回收器入口1
我們知道,垃圾回收器實際是在時刻都在工作的,比如minor空間不足時,觸發一次MinorGC,當老年代空間不足時,觸發MajorGC。但要說我們主動觸發GC,則可以直接呼叫 System.gc(); 即可完成一次gc工作。
// jdk/src/share/native/java/lang/Runtime.c JNIEXPORT void JNICALL Java_java_lang_Runtime_gc(JNIEnv *env, jobject this) { JVM_GC(); } // jdk,hosport, share/vm/prims/jvm.h JNIEXPORT void JNICALL JVM_GC(void); // hotspot/src/share/vm/prims/jvm.cpp JVM_ENTRY_NO_ENV(void, JVM_GC(void)) JVMWrapper("JVM_GC"); if (!DisableExplicitGC) { // 呼叫對應的gc實現,collect 回收記憶體 Universe::heap()->collect(GCCause::_java_lang_system_gc); } JVM_END
其中,heap() 和 collect() 都是有許多實現版本的,即根據選擇的垃圾回收器的不同,而執行不同的邏輯。
具體的heap()選擇,需要依賴於外部設定。我們看兩個具體的 heap 由來。一個是預設的,一個PS的,一個G1的。
// share/memory/universe.cpp // The particular choice of collected heap. static CollectedHeap* heap() { return _collectedHeap; } // share/vm/gc_implemention/parallelScavenge/parallelScavengeHeap.cpp ParallelScavengeHeap* ParallelScavengeHeap::heap() { assert(_psh != NULL, "Uninitialized access to ParallelScavengeHeap::heap()"); assert(_psh->kind() == CollectedHeap::ParallelScavengeHeap, "not a parallel scavenge heap"); return _psh; } // share/vm/gc_implemention/g1/g1CollectionHeap.cpp G1CollectedHeap* G1CollectedHeap::heap() { assert(_sh->kind() == CollectedHeap::G1CollectedHeap, "not a garbage-first heap"); return _g1h; }
具體記憶體回收由 collect完成,而每個垃圾回收器有各自的實現。我們就看3個實現,1.預設新生代回收;2.多執行緒新生代回收;3.G1新生代回收;只需看個入口框架,細節留待後續完成。
// 1. defNew 回收 // share/memory/defNewGeneration.cpp void DefNewGeneration::collect(bool full, bool clear_all_soft_refs, size_t size, bool is_tlab) { assert(full || size > 0, "otherwise we don't want to collect"); GenCollectedHeap* gch = GenCollectedHeap::heap(); _gc_timer->register_gc_start(); DefNewTracer gc_tracer; gc_tracer.report_gc_start(gch->gc_cause(), _gc_timer->gc_start()); _next_gen = gch->next_gen(this); // If the next generation is too full to accommodate promotion // from this generation, pass on collection; let the next generation // do it. if (!collection_attempt_is_safe()) { if (Verbose && PrintGCDetails) { gclog_or_tty->print(" :: Collection attempt not safe :: "); } gch->set_incremental_collection_failed(); // Slight lie: we did not even attempt one return; } assert(to()->is_empty(), "Else not collection_attempt_is_safe"); init_assuming_no_promotion_failure(); GCTraceTime t1(GCCauseString("GC", gch->gc_cause()), PrintGC && !PrintGCDetails, true, NULL); // Capture heap used before collection (for printing). size_t gch_prev_used = gch->used(); gch->trace_heap_before_gc(&gc_tracer); SpecializationStats::clear(); // These can be shared for all code paths IsAliveClosure is_alive(this); ScanWeakRefClosure scan_weak_ref(this); age_table()->clear(); to()->clear(SpaceDecorator::Mangle); gch->rem_set()->prepare_for_younger_refs_iterate(false); assert(gch->no_allocs_since_save_marks(0), "save marks have not been newly set."); // Not very pretty. CollectorPolicy* cp = gch->collector_policy(); FastScanClosure fsc_with_no_gc_barrier(this, false); FastScanClosure fsc_with_gc_barrier(this, true); KlassScanClosure klass_scan_closure(&fsc_with_no_gc_barrier, gch->rem_set()->klass_rem_set()); set_promo_failure_scan_stack_closure(&fsc_with_no_gc_barrier); FastEvacuateFollowersClosure evacuate_followers(gch, _level, this, &fsc_with_no_gc_barrier, &fsc_with_gc_barrier); assert(gch->no_allocs_since_save_marks(0), "save marks have not been newly set."); int so = SharedHeap::SO_AllClasses | SharedHeap::SO_Strings | SharedHeap::SO_CodeCache; gch->gen_process_strong_roots(_level, true, // Process younger gens, if any, // as strong roots. true, // activate StrongRootsScope true, // is scavenging SharedHeap::ScanningOption(so), &fsc_with_no_gc_barrier, true, // walk *all* scavengable nmethods &fsc_with_gc_barrier, &klass_scan_closure); // "evacuate followers". evacuate_followers.do_void(); FastKeepAliveClosure keep_alive(this, &scan_weak_ref); ReferenceProcessor* rp = ref_processor(); rp->setup_policy(clear_all_soft_refs); const ReferenceProcessorStats& stats = rp->process_discovered_references(&is_alive, &keep_alive, &evacuate_followers, NULL, _gc_timer); gc_tracer.report_gc_reference_stats(stats); if (!_promotion_failed) { // Swap the survivor spaces. eden()->clear(SpaceDecorator::Mangle); from()->clear(SpaceDecorator::Mangle); if (ZapUnusedHeapArea) { // This is now done here because of the piece-meal mangling which // can check for valid mangling at intermediate points in the // collection(s). When a minor collection fails to collect // sufficient space resizing of the young generation can occur // an redistribute the spaces in the young generation. Mangle // here so that unzapped regions don't get distributed to // other spaces. to()->mangle_unused_area(); } swap_spaces(); assert(to()->is_empty(), "to space should be empty now"); adjust_desired_tenuring_threshold(); // A successful scavenge should restart the GC time limit count which is // for full GC's. AdaptiveSizePolicy* size_policy = gch->gen_policy()->size_policy(); size_policy->reset_gc_overhead_limit_count(); if (PrintGC && !PrintGCDetails) { gch->print_heap_change(gch_prev_used); } assert(!gch->incremental_collection_failed(), "Should be clear"); } else { assert(_promo_failure_scan_stack.is_empty(), "post condition"); _promo_failure_scan_stack.clear(true); // Clear cached segments. remove_forwarding_pointers(); if (PrintGCDetails) { gclog_or_tty->print(" (promotion failed) "); } // Add to-space to the list of space to compact // when a promotion failure has occurred. In that // case there can be live objects in to-space // as a result of a partial evacuation of eden // and from-space. swap_spaces(); // For uniformity wrt ParNewGeneration. from()->set_next_compaction_space(to()); gch->set_incremental_collection_failed(); // Inform the next generation that a promotion failure occurred. _next_gen->promotion_failure_occurred(); gc_tracer.report_promotion_failed(_promotion_failed_info); // Reset the PromotionFailureALot counters. NOT_PRODUCT(Universe::heap()->reset_promotion_should_fail();) } // set new iteration safe limit for the survivor spaces from()->set_concurrent_iteration_safe_limit(from()->top()); to()->set_concurrent_iteration_safe_limit(to()->top()); SpecializationStats::print(); // We need to use a monotonically non-decreasing time in ms // or we will see time-warp warnings and os::javaTimeMillis() // does not guarantee monotonicity. jlong now = os::javaTimeNanos() / NANOSECS_PER_MILLISEC; update_time_of_last_gc(now); gch->trace_heap_after_gc(&gc_tracer); gc_tracer.report_tenuring_threshold(tenuring_threshold()); _gc_timer->register_gc_end(); gc_tracer.report_gc_end(_gc_timer->gc_end(), _gc_timer->time_partitions()); } // 2. parNew 回收 // share/vm/gc_implemention/parNew/parNewGeneration.cpp void ParNewGeneration::collect(bool full, bool clear_all_soft_refs, size_t size, bool is_tlab) { assert(full || size > 0, "otherwise we don't want to collect"); // 獲得堆空間的指標 GenCollectedHeap* gch = GenCollectedHeap::heap(); _gc_timer->register_gc_start(); assert(gch->kind() == CollectedHeap::GenCollectedHeap, "not a CMS generational heap"); AdaptiveSizePolicy* size_policy = gch->gen_policy()->size_policy(); FlexibleWorkGang* workers = gch->workers(); assert(workers != NULL, "Need workgang for parallel work"); int active_workers = AdaptiveSizePolicy::calc_active_workers(workers->total_workers(), workers->active_workers(), Threads::number_of_non_daemon_threads()); workers->set_active_workers(active_workers); assert(gch->n_gens() == 2, "Par collection currently only works with single older gen."); _next_gen = gch->next_gen(this); // Do we have to avoid promotion_undo? if (gch->collector_policy()->is_concurrent_mark_sweep_policy()) { set_avoid_promotion_undo(true); } // If the next generation is too full to accommodate worst-case promotion // from this generation, pass on collection; let the next generation // do it. if (!collection_attempt_is_safe()) { gch->set_incremental_collection_failed(); // slight lie, in that we did not even attempt one return; } assert(to()->is_empty(), "Else not collection_attempt_is_safe"); ParNewTracer gc_tracer; gc_tracer.report_gc_start(gch->gc_cause(), _gc_timer->gc_start()); gch->trace_heap_before_gc(&gc_tracer); init_assuming_no_promotion_failure(); if (UseAdaptiveSizePolicy) { set_survivor_overflow(false); size_policy->minor_collection_begin(); } GCTraceTime t1(GCCauseString("GC", gch->gc_cause()), PrintGC && !PrintGCDetails, true, NULL); // Capture heap used before collection (for printing). size_t gch_prev_used = gch->used(); SpecializationStats::clear(); age_table()->clear(); to()->clear(SpaceDecorator::Mangle); gch->save_marks(); assert(workers != NULL, "Need parallel worker threads."); int n_workers = active_workers; // Set the correct parallelism (number of queues) in the reference processor ref_processor()->set_active_mt_degree(n_workers); // Always set the terminator for the active number of workers // because only those workers go through the termination protocol. ParallelTaskTerminator _term(n_workers, task_queues()); ParScanThreadStateSet thread_state_set(workers->active_workers(), *to(), *this, *_next_gen, *task_queues(), _overflow_stacks, desired_plab_sz(), _term); ParNewGenTask tsk(this, _next_gen, reserved().end(), &thread_state_set); gch->set_par_threads(n_workers); gch->rem_set()->prepare_for_younger_refs_iterate(true); // It turns out that even when we're using 1 thread, doing the work in a // separate thread causes wide variance in run times. We can't help this // in the multi-threaded case, but we special-case n=1 here to get // repeatable measurements of the 1-thread overhead of the parallel code. if (n_workers > 1) { // 使用GcRoots 可達性分析法 GenCollectedHeap::StrongRootsScope srs(gch); workers->run_task(&tsk); } else { GenCollectedHeap::StrongRootsScope srs(gch); tsk.work(0); } thread_state_set.reset(0 /* Bad value in debug if not reset */, promotion_failed()); // Process (weak) reference objects found during scavenge. ReferenceProcessor* rp = ref_processor(); IsAliveClosure is_alive(this); ScanWeakRefClosure scan_weak_ref(this); KeepAliveClosure keep_alive(&scan_weak_ref); ScanClosure scan_without_gc_barrier(this, false); ScanClosureWithParBarrier scan_with_gc_barrier(this, true); set_promo_failure_scan_stack_closure(&scan_without_gc_barrier); EvacuateFollowersClosureGeneral evacuate_followers(gch, _level, &scan_without_gc_barrier, &scan_with_gc_barrier); rp->setup_policy(clear_all_soft_refs); // Can the mt_degree be set later (at run_task() time would be best)? rp->set_active_mt_degree(active_workers); ReferenceProcessorStats stats; if (rp->processing_is_mt()) { ParNewRefProcTaskExecutor task_executor(*this, thread_state_set); stats = rp->process_discovered_references(&is_alive, &keep_alive, &evacuate_followers, &task_executor, _gc_timer); } else { thread_state_set.flush(); gch->set_par_threads(0); // 0 ==> non-parallel. gch->save_marks(); stats = rp->process_discovered_references(&is_alive, &keep_alive, &evacuate_followers, NULL, _gc_timer); } gc_tracer.report_gc_reference_stats(stats); if (!promotion_failed()) { // Swap the survivor spaces. eden()->clear(SpaceDecorator::Mangle); from()->clear(SpaceDecorator::Mangle); if (ZapUnusedHeapArea) { // This is now done here because of the piece-meal mangling which // can check for valid mangling at intermediate points in the // collection(s). When a minor collection fails to collect // sufficient space resizing of the young generation can occur // an redistribute the spaces in the young generation. Mangle // here so that unzapped regions don't get distributed to // other spaces. to()->mangle_unused_area(); } swap_spaces(); // A successful scavenge should restart the GC time limit count which is // for full GC's. size_policy->reset_gc_overhead_limit_count(); assert(to()->is_empty(), "to space should be empty now"); adjust_desired_tenuring_threshold(); } else { handle_promotion_failed(gch, thread_state_set, gc_tracer); } // set new iteration safe limit for the survivor spaces from()->set_concurrent_iteration_safe_limit(from()->top()); to()->set_concurrent_iteration_safe_limit(to()->top()); if (ResizePLAB) { plab_stats()->adjust_desired_plab_sz(n_workers); } // 輸出gc日誌 if (PrintGC && !PrintGCDetails) { gch->print_heap_change(gch_prev_used); } if (PrintGCDetails && ParallelGCVerbose) { TASKQUEUE_STATS_ONLY(thread_state_set.print_termination_stats()); TASKQUEUE_STATS_ONLY(thread_state_set.print_taskqueue_stats()); } if (UseAdaptiveSizePolicy) { size_policy->minor_collection_end(gch->gc_cause()); size_policy->avg_survived()->sample(from()->used()); } // We need to use a monotonically non-deccreasing time in ms // or we will see time-warp warnings and os::javaTimeMillis() // does not guarantee monotonicity. jlong now = os::javaTimeNanos() / NANOSECS_PER_MILLISEC; update_time_of_last_gc(now); SpecializationStats::print(); rp->set_enqueuing_is_done(true); if (rp->processing_is_mt()) { ParNewRefProcTaskExecutor task_executor(*this, thread_state_set); rp->enqueue_discovered_references(&task_executor); } else { rp->enqueue_discovered_references(NULL); } rp->verify_no_references_recorded(); gch->trace_heap_after_gc(&gc_tracer); gc_tracer.report_tenuring_threshold(tenuring_threshold()); _gc_timer->register_gc_end(); gc_tracer.report_gc_end(_gc_timer->gc_end(), _gc_timer->time_partitions()); } // 3. 分代收集回收 // share/vm/memory/genCollectedHeap.cpp void GenCollectedHeap::collect(GCCause::Cause cause) { // 判斷是否需要進行FGC, 依據是 cms 收集器且 cause==_gc_locker if (should_do_concurrent_full_gc(cause)) { #if INCLUDE_ALL_GCS // mostly concurrent full collection collect_mostly_concurrent(cause); #else // INCLUDE_ALL_GCS ShouldNotReachHere(); #endif // INCLUDE_ALL_GCS } else { #ifdef ASSERT if (cause == GCCause::_scavenge_alot) { // minor collection only // 新生代gc collect(cause, 0); } else { // Stop-the-world full collection collect(cause, n_gens() - 1); } #else // Stop-the-world full collection collect(cause, n_gens() - 1); #endif } } void GenCollectedHeap::collect(GCCause::Cause cause, int max_level) { // The caller doesn't have the Heap_lock assert(!Heap_lock->owned_by_self(), "this thread should not own the Heap_lock"); MutexLocker ml(Heap_lock); // 上鎖收集記憶體 collect_locked(cause, max_level); } // this is the private collection interface // The Heap_lock is expected to be held on entry. void GenCollectedHeap::collect_locked(GCCause::Cause cause, int max_level) { // Read the GC count while holding the Heap_lock unsigned int gc_count_before = total_collections(); unsigned int full_gc_count_before = total_full_collections(); { MutexUnlocker mu(Heap_lock); // give up heap lock, execute gets it back // 整個gc過程就由 VM_GenCollectFull 去實現了,而其中最重要的則是 doIt() 方法的實現 VM_GenCollectFull op(gc_count_before, full_gc_count_before, cause, max_level); // 交給虛擬機器執行緒完成該工作 VMThread::execute(&op); } } // 4. G1回收 // share/vm/gc_implemention/g1/g1CollectedHeap.cpp void G1CollectedHeap::collect(GCCause::Cause cause) { assert_heap_not_locked(); unsigned int gc_count_before; unsigned int old_marking_count_before; bool retry_gc; do { retry_gc = false; { MutexLocker ml(Heap_lock); // Read the GC count while holding the Heap_lock gc_count_before = total_collections(); old_marking_count_before = _old_marking_cycles_started; } // FullGc 判定 if (should_do_concurrent_full_gc(cause)) { // Schedule an initial-mark evacuation pause that will start a // concurrent cycle. We're setting word_size to 0 which means that // we are not requesting a post-GC allocation. // 整個 FGC 過程由 VM_G1IncCollectionPause 完成 VM_G1IncCollectionPause op(gc_count_before, 0, /* word_size */ true, /* should_initiate_conc_mark */ g1_policy()->max_pause_time_ms(), cause); VMThread::execute(&op); if (!op.pause_succeeded()) { if (old_marking_count_before == _old_marking_cycles_started) { retry_gc = op.should_retry_gc(); } else { // A Full GC happened while we were trying to schedule the // initial-mark GC. No point in starting a new cycle given // that the whole heap was collected anyway. } if (retry_gc) { if (GC_locker::is_active_and_needs_gc()) { GC_locker::stall_until_clear(); } } } } else { if (cause == GCCause::_gc_locker DEBUG_ONLY(|| cause == GCCause::_scavenge_alot)) { // Schedule a standard evacuation pause. We're setting word_size // to 0 which means that we are not requesting a post-GC allocation. VM_G1IncCollectionPause op(gc_count_before, 0, /* word_size */ false, /* should_initiate_conc_mark */ g1_policy()->max_pause_time_ms(), cause); VMThread::execute(&op); } else { // Schedule a Full GC. VM_G1CollectFull op(gc_count_before, old_marking_count_before, cause); VMThread::execute(&op); } } } while (retry_gc); }
以上是幾個收集的實現入門框架,從中我們可以窺得一些實現方式。尤其是對於 單執行緒類的收集器,基本思路已經形成。只是具體如何,還得看官們自花心思。
除了要了解gc從何處開始執行,我們應該還需要知道如何選擇收集器,以及他們是如何初始化的。這自然是在jvm啟動時完成的。
// universe初始化 // share/vm/runtime/init.cpp jint init_globals() { HandleMark hm; management_init(); bytecodes_init(); classLoader_init(); codeCache_init(); VM_Version_init(); os_init_globals(); stubRoutines_init1(); // gc 初始化入口 jint status = universe_init(); // dependent on codeCache_init and // stubRoutines_init1 and metaspace_init. if (status != JNI_OK) return status; interpreter_init(); // before any methods loaded invocationCounter_init(); // before any methods loaded marksweep_init(); accessFlags_init(); templateTable_init(); InterfaceSupport_init(); SharedRuntime::generate_stubs(); universe2_init(); // dependent on codeCache_init and stubRoutines_init1 referenceProcessor_init(); jni_handles_init(); #if INCLUDE_VM_STRUCTS vmStructs_init(); #endif // INCLUDE_VM_STRUCTS vtableStubs_init(); InlineCacheBuffer_init(); compilerOracle_init(); compilationPolicy_init(); compileBroker_init(); VMRegImpl::set_regName(); if (!universe_post_init()) { return JNI_ERR; } javaClasses_init(); // must happen after vtable initialization stubRoutines_init2(); // note: StubRoutines need 2-phase init // All the flags that get adjusted by VM_Version_init and os::init_2 // have been set so dump the flags now. if (PrintFlagsFinal) { CommandLineFlags::printFlags(tty, false); } return JNI_OK; } // memory/universe.cpp jint universe_init() { assert(!Universe::_fully_initialized, "called after initialize_vtables"); guarantee(1 << LogHeapWordSize == sizeof(HeapWord), "LogHeapWordSize is incorrect."); guarantee(sizeof(oop) >= sizeof(HeapWord), "HeapWord larger than oop?"); guarantee(sizeof(oop) % sizeof(HeapWord) == 0, "oop size is not not a multiple of HeapWord size"); TraceTime timer("Genesis", TraceStartupTime); GC_locker::lock(); // do not allow gc during bootstrapping JavaClasses::compute_hard_coded_offsets(); jint status = Universe::initialize_heap(); if (status != JNI_OK) { return status; } Metaspace::global_initialize(); // Create memory for metadata. Must be after initializing heap for // DumpSharedSpaces. ClassLoaderData::init_null_class_loader_data(); // We have a heap so create the Method* caches before // Metaspace::initialize_shared_spaces() tries to populate them. Universe::_finalizer_register_cache = new LatestMethodCache(); Universe::_loader_addClass_cache = new LatestMethodCache(); Universe::_pd_implies_cache = new LatestMethodCache(); if (UseSharedSpaces) { // Read the data structures supporting the shared spaces (shared // system dictionary, symbol table, etc.). After that, access to // the file (other than the mapped regions) is no longer needed, and // the file is closed. Closing the file does not affect the // currently mapped regions. MetaspaceShared::initialize_shared_spaces(); StringTable::create_table(); } else { SymbolTable::create_table(); StringTable::create_table(); ClassLoader::create_package_info_table(); } return JNI_OK; } // hotspot/src/share/vm/memory/universe.cpp: jint Universe::initialize_heap() { if (UseParallelGC) { #if INCLUDE_ALL_GCS Universe::_collectedHeap = new ParallelScavengeHeap(); #else // INCLUDE_ALL_GCS fatal("UseParallelGC not supported in this VM."); #endif // INCLUDE_ALL_GCS } else if (UseG1GC) { #if INCLUDE_ALL_GCS G1CollectorPolicy* g1p = new G1CollectorPolicy(); g1p->initialize_all(); G1CollectedHeap* g1h = new G1CollectedHeap(g1p); Universe::_collectedHeap = g1h; #else // INCLUDE_ALL_GCS fatal("UseG1GC not supported in java kernel vm."); #endif // INCLUDE_ALL_GCS } else { GenCollectorPolicy *gc_policy; if (UseSerialGC) { gc_policy = new MarkSweepPolicy(); } else if (UseConcMarkSweepGC) { #if INCLUDE_ALL_GCS if (UseAdaptiveSizePolicy) { gc_policy = new ASConcurrentMarkSweepPolicy(); } else { gc_policy = new ConcurrentMarkSweepPolicy(); } #else // INCLUDE_ALL_GCS fatal("UseConcMarkSweepGC not supported in this VM."); #endif // INCLUDE_ALL_GCS } else { // default old generation gc_policy = new MarkSweepPolicy(); } gc_policy->initialize_all(); Universe::_collectedHeap = new GenCollectedHeap(gc_policy); } jint status = Universe::heap()->initialize(); if (status != JNI_OK) { return status; } #ifdef _LP64 if (UseCompressedOops) { // Subtract a page because something can get allocated at heap base. // This also makes implicit null checking work, because the // memory+1 page below heap_base needs to cause a signal. // See needs_explicit_null_check. // Only set the heap base for compressed oops because it indicates // compressed oops for pstack code. bool verbose = PrintCompressedOopsMode || (PrintMiscellaneous && Verbose); if (verbose) { tty->cr(); tty->print("heap address: " PTR_FORMAT ", size: " SIZE_FORMAT " MB", Universe::heap()->base(), Universe::heap()->reserved_region().byte_size()/M); } if (((uint64_t)Universe::heap()->reserved_region().end() > OopEncodingHeapMax)) { // Can't reserve heap below 32Gb. // keep the Universe::narrow_oop_base() set in Universe::reserve_heap() Universe::set_narrow_oop_shift(LogMinObjAlignmentInBytes); if (verbose) { tty->print(", %s: "PTR_FORMAT, narrow_oop_mode_to_string(HeapBasedNarrowOop), Universe::narrow_oop_base()); } } else { Universe::set_narrow_oop_base(0); if (verbose) { tty->print(", %s", narrow_oop_mode_to_string(ZeroBasedNarrowOop)); } #ifdef _WIN64 if (!Universe::narrow_oop_use_implicit_null_checks()) { // Don't need guard page for implicit checks in indexed addressing // mode with zero based Compressed Oops. Universe::set_narrow_oop_use_implicit_null_checks(true); } #endif // _WIN64 if((uint64_t)Universe::heap()->reserved_region().end() > UnscaledOopHeapMax) { // Can't reserve heap below 4Gb. Universe::set_narrow_oop_shift(LogMinObjAlignmentInBytes); } else { Universe::set_narrow_oop_shift(0); if (verbose) { tty->print(", %s", narrow_oop_mode_to_string(UnscaledNarrowOop)); } } } if (verbose) { tty->cr(); tty->cr(); } Universe::set_narrow_ptrs_base(Universe::narrow_oop_base()); } // Universe::narrow_oop_base() is one page below the heap. assert((intptr_t)Universe::narrow_oop_base() <= (intptr_t)(Universe::heap()->base() - os::vm_page_size()) || Universe::narrow_oop_base() == NULL, "invalid value"); assert(Universe::narrow_oop_shift() == LogMinObjAlignmentInBytes || Universe::narrow_oop_shift() == 0, "invalid value"); #endif // We will never reach the CATCH below since Exceptions::_throw will cause // the VM to exit if an exception is thrown during initialization if (UseTLAB) { assert(Universe::heap()->supports_tlab_allocation(), "Should support thread-local allocation buffers"); ThreadLocalAllocBuffer::startup_initialization(); } return JNI_OK; }
3. 一個記憶體回收器的工作示例
該部分我們以一某個垃圾收集器的實現過程,來了解gc是如何完成的。這自然算不得真正的瞭解gc原理,但是有其一定的作用。
首先,一般的gc動作都會有獨立的vm執行緒,也就是我們通過jstack檢視時看到的gc執行緒。當然了,在程式碼中我們看到的是 VMThread. vm執行緒執行垃圾回收:
// share/vm/runtime/vmThread.cpp void VMThread::execute(VM_Operation* op) { Thread* t = Thread::current(); if (!t->is_VM_thread()) { SkipGCALot sgcalot(t); // avoid re-entrant attempts to gc-a-lot // JavaThread or WatcherThread bool concurrent = op->evaluate_concurrently(); // only blocking VM operations need to verify the caller's safepoint state: if (!concurrent) { t->check_for_valid_safepoint_state(true); } // New request from Java thread, evaluate prologue if (!op->doit_prologue()) { return; // op was cancelled } // Setup VM_operations for execution op->set_calling_thread(t, Thread::get_priority(t)); // It does not make sense to execute the epilogue, if the VM operation object is getting // deallocated by the VM thread. bool execute_epilog = !op->is_cheap_allocated(); assert(!concurrent || op->is_cheap_allocated(), "concurrent => cheap_allocated"); // Get ticket number for non-concurrent VM operations int ticket = 0; if (!concurrent) { ticket = t->vm_operation_ticket(); } // Add VM operation to list of waiting threads. We are guaranteed not to block while holding the // VMOperationQueue_lock, so we can block without a safepoint check. This allows vm operation requests // to be queued up during a safepoint synchronization. { VMOperationQueue_lock->lock_without_safepoint_check(); bool ok = _vm_queue->add(op); op->set_timestamp(os::javaTimeMillis()); VMOperationQueue_lock->notify(); VMOperationQueue_lock->unlock(); // VM_Operation got skipped if (!ok) { assert(concurrent, "can only skip concurrent tasks"); if (op->is_cheap_allocated()) delete op; return; } } if (!concurrent) { // Wait for completion of request (non-concurrent) // Note: only a JavaThread triggers the safepoint check when locking MutexLocker mu(VMOperationRequest_lock); while(t->vm_operation_completed_count() < ticket) { VMOperationRequest_lock->wait(!t->is_Java_thread()); } } if (execute_epilog) { op->doit_epilogue(); } } else { // invoked by VM thread; usually nested VM operation assert(t->is_VM_thread(), "must be a VM thread"); VM_Operation* prev_vm_operation = vm_operation(); if (prev_vm_operation != NULL) { // Check the VM operation allows nested VM operation. This normally not the case, e.g., the compiler // does not allow nested scavenges or compiles. if (!prev_vm_operation->allow_nested_vm_operations()) { fatal(err_msg("Nested VM operation %s requested by operation %s", op->name(), vm_operation()->name())); } op->set_calling_thread(prev_vm_operation->calling_thread(), prev_vm_operation->priority()); } EventMark em("Executing %s VM operation: %s", prev_vm_operation ? "nested" : "", op->name()); // Release all internal handles after operation is evaluated HandleMark hm(t); _cur_vm_operation = op; if (op->evaluate_at_safepoint() && !SafepointSynchronize::is_at_safepoint()) { SafepointSynchronize::begin(); op->evaluate(); SafepointSynchronize::end(); } else { op->evaluate(); } // Free memory if needed if (op->is_cheap_allocated()) delete op; _cur_vm_operation = prev_vm_operation; } }
我們看前面的 GenCollectedHeap 的垃圾回收方法,其最終向 vmThread 提交了一個 VM_GenCollectFull 的op任務,而其核心實現是在 doit() 方法中。 分代垃圾回收,由vm執行緒執行。
// vm/gc_implemention/shared/vmGCOperations.cpp void VM_GenCollectFull::doit() { SvcGCMarker sgcm(SvcGCMarker::FULL); GenCollectedHeap* gch = GenCollectedHeap::heap(); GCCauseSetter gccs(gch, _gc_cause); // 清理軟引用,再做回收 gch->do_full_collection(gch->must_clear_all_soft_refs(), _max_level); } // memory/genCollectedHeap.cpp void GenCollectedHeap::do_full_collection(bool clear_all_soft_refs, int max_level) { int local_max_level; if (!incremental_collection_will_fail(false /* don't consult_young */) && gc_cause() == GCCause::_gc_locker) { local_max_level = 0; } else { local_max_level = max_level; } // 具體回收動作 do_collection(true /* full */, clear_all_soft_refs /* clear_all_soft_refs */, 0 /* size */, false /* is_tlab */, local_max_level /* max_level */); // Hack XXX FIX ME !!! // A scavenge may not have been attempted, or may have // been attempted and failed, because the old gen was too full // 觸發一次 FGC if (local_max_level == 0 && gc_cause() == GCCause::_gc_locker && incremental_collection_will_fail(false /* don't consult_young */)) { if (PrintGCDetails) { gclog_or_tty->print_cr("GC locker: Trying a full collection " "because scavenge failed"); } // This time allow the old gen to be collected as well do_collection(true /* full */, clear_all_soft_refs /* clear_all_soft_refs */, 0 /* size */, false /* is_tlab */, n_gens() - 1 /* max_level */); } } // memory/genCollectedHeap.cpp void GenCollectedHeap::do_collection(bool full, bool clear_all_soft_refs, size_t size, bool is_tlab, int max_level) { bool prepared_for_verification = false; ResourceMark rm; DEBUG_ONLY(Thread* my_thread = Thread::current();) assert(SafepointSynchronize::is_at_safepoint(), "should be at safepoint"); assert(my_thread->is_VM_thread() || my_thread->is_ConcurrentGC_thread(), "incorrect thread type capability"); assert(Heap_lock->is_locked(), "the requesting thread should have the Heap_lock"); guarantee(!is_gc_active(), "collection is not reentrant"); assert(max_level < n_gens(), "sanity check"); if (GC_locker::check_active_before_gc()) { return; // GC is disabled (e.g. JNI GetXXXCritical operation) } const bool do_clear_all_soft_refs = clear_all_soft_refs || collector_policy()->should_clear_all_soft_refs(); ClearedAllSoftRefs casr(do_clear_all_soft_refs, collector_policy()); const size_t metadata_prev_used = MetaspaceAux::allocated_used_bytes(); print_heap_before_gc(); { FlagSetting fl(_is_gc_active, true); bool complete = full && (max_level == (n_gens()-1)); const char* gc_cause_prefix = complete ? "Full GC" : "GC"; gclog_or_tty->date_stamp(PrintGC && PrintGCDateStamps); TraceCPUTime tcpu(PrintGCDetails, true, gclog_or_tty); GCTraceTime t(GCCauseString(gc_cause_prefix, gc_cause()), PrintGCDetails, false, NULL); gc_prologue(complete); increment_total_collections(complete); size_t gch_prev_used = used(); int starting_level = 0; if (full) { // Search for the oldest generation which will collect all younger // generations, and start collection loop there. for (int i = max_level; i >= 0; i--) { if (_gens[i]->full_collects_younger_generations()) { starting_level = i; break; } } } bool must_restore_marks_for_biased_locking = false; int max_level_collected = starting_level; for (int i = starting_level; i <= max_level; i++) { if (_gens[i]->should_collect(full, size, is_tlab)) { // 升級FGC if (i == n_gens() - 1) { // a major collection is to happen if (!complete) { // The full_collections increment was missed above. increment_total_full_collections(); } pre_full_gc_dump(NULL); // do any pre full gc dumps } // Timer for individual generations. Last argument is false: no CR // FIXME: We should try to start the timing earlier to cover more of the GC pause GCTraceTime t1(_gens[i]->short_name(), PrintGCDetails, false, NULL); TraceCollectorStats tcs(_gens[i]->counters()); TraceMemoryManagerStats tmms(_gens[i]->kind(),gc_cause()); size_t prev_used = _gens[i]->used(); _gens[i]->stat_record()->invocations++; _gens[i]->stat_record()->accumulated_time.start(); // Must be done anew before each collection because // a previous collection will do mangling and will // change top of some spaces. record_gen_tops_before_GC(); if (PrintGC && Verbose) { gclog_or_tty->print("level=%d invoke=%d size=" SIZE_FORMAT, i, _gens[i]->stat_record()->invocations, size*HeapWordSize); } if (VerifyBeforeGC && i >= VerifyGCLevel && total_collections() >= VerifyGCStartAt) { HandleMark hm; // Discard invalid handles created during verification if (!prepared_for_verification) { prepare_for_verify(); prepared_for_verification = true; } Universe::verify(" VerifyBeforeGC:"); } COMPILER2_PRESENT(DerivedPointerTable::clear()); if (!must_restore_marks_for_biased_locking && _gens[i]->performs_in_place_marking()) { // We perform this mark word preservation work lazily // because it's only at this point that we know whether we // absolutely have to do it; we want to avoid doing it for // scavenge-only collections where it's unnecessary must_restore_marks_for_biased_locking = true; BiasedLocking::preserve_marks(); } // Do collection work { // Note on ref discovery: For what appear to be historical reasons, // GCH enables and disabled (by enqueing) refs discovery. // In the future this should be moved into the generation's // collect method so that ref discovery and enqueueing concerns // are local to a generation. The collect method could return // an appropriate indication in the case that notification on // the ref lock was needed. This will make the treatment of // weak refs more uniform (and indeed remove such concerns // from GCH). XXX HandleMark hm; // Discard invalid handles created during gc save_marks(); // save marks for all gens // We want to discover references, but not process them yet. // This mode is disabled in process_discovered_references if the // generation does some collection work, or in // enqueue_discovered_references if the generation returns // without doing any work. ReferenceProcessor* rp = _gens[i]->ref_processor(); // If the discovery of ("weak") refs in this generation is // atomic wrt other collectors in this configuration, we // are guaranteed to have empty discovered ref lists. if (rp->discovery_is_atomic()) { rp->enable_discovery(true /*verify_disabled*/, true /*verify_no_refs*/); rp->setup_policy(do_clear_all_soft_refs); } else { // collect() below will enable discovery as appropriate } // 每個年代的物件各自回收 _gens[i]->collect(full, do_clear_all_soft_refs, size, is_tlab); if (!rp->enqueuing_is_done()) { rp->enqueue_discovered_references(); } else { rp->set_enqueuing_is_done(false); } rp->verify_no_references_recorded(); } max_level_collected = i; // 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; } } } COMPILER2_PRESENT(DerivedPointerTable::update_pointers()); _gens[i]->stat_record()->accumulated_time.stop(); update_gc_stats(i, full); if (VerifyAfterGC && i >= VerifyGCLevel && total_collections() >= VerifyGCStartAt) { HandleMark hm; // Discard invalid handles created during verification Universe::verify(" VerifyAfterGC:"); } if (PrintGCDetails) { gclog_or_tty->print(":"); _gens[i]->print_heap_change(prev_used); } } } // Update "complete" boolean wrt what actually transpired -- // for instance, a promotion failure could have led to // a whole heap collection. complete = complete || (max_level_collected == n_gens() - 1); if (complete) { // We did a "major" collection // FIXME: See comment at pre_full_gc_dump call post_full_gc_dump(NULL); // do any post full gc dumps } if (PrintGCDetails) { print_heap_change(gch_prev_used); // Print metaspace info for full GC with PrintGCDetails flag. if (complete) { MetaspaceAux::print_metaspace_change(metadata_prev_used); } } for (int j = max_level_collected; j >= 0; j -= 1) { // Adjust generation sizes. _gens[j]->compute_new_size(); } if (complete) { // Delete metaspaces for unloaded class loaders and clean up loader_data graph ClassLoaderDataGraph::purge(); MetaspaceAux::verify_metrics(); // Resize the metaspace capacity after full collections MetaspaceGC::compute_new_size(); update_full_collections_completed(); } // Track memory usage and detect low memory after GC finishes MemoryService::track_memory_usage(); gc_epilogue(complete); if (must_restore_marks_for_biased_locking) { BiasedLocking::restore_marks(); } } AdaptiveSizePolicy* sp = gen_policy()->size_policy(); AdaptiveSizePolicyOutput(sp, total_collections()); print_heap_after_gc(); #ifdef TRACESPINNING ParallelTaskTerminator::print_termination_counts(); #endif } // memory/generation.cpp void OneContigSpaceCardGeneration::collect(bool full, bool clear_all_soft_refs, size_t size, bool is_tlab) { GenCollectedHeap* gch = GenCollectedHeap::heap(); SpecializationStats::clear(); // Temporarily expand the span of our ref processor, so // refs discovery is over the entire heap, not just this generation ReferenceProcessorSpanMutator x(ref_processor(), gch->reserved_region()); STWGCTimer* gc_timer = GenMarkSweep::gc_timer(); gc_timer->register_gc_start(); SerialOldTracer* gc_tracer = GenMarkSweep::gc_tracer(); gc_tracer->report_gc_start(gch->gc_cause(), gc_timer->gc_start()); // 標記清除演算法呼叫 GenMarkSweep::invoke_at_safepoint(_level, ref_processor(), clear_all_soft_refs); gc_timer->register_gc_end(); gc_tracer->report_gc_end(gc_timer->gc_end(), gc_timer->time_partitions()); SpecializationStats::print(); } // memory/genMarkSweep.cpp void GenMarkSweep::invoke_at_safepoint(int level, ReferenceProcessor* rp, bool clear_all_softrefs) { guarantee(level == 1, "We always collect both old and young."); assert(SafepointSynchronize::is_at_safepoint(), "must be at a safepoint"); GenCollectedHeap* gch = GenCollectedHeap::heap(); #ifdef ASSERT if (gch->collector_policy()->should_clear_all_soft_refs()) { assert(clear_all_softrefs, "Policy should have been checked earlier"); } #endif // hook up weak ref data so it can be used during Mark-Sweep assert(ref_processor() == NULL, "no stomping"); assert(rp != NULL, "should be non-NULL"); _ref_processor = rp; rp->setup_policy(clear_all_softrefs); GCTraceTime t1(GCCauseString("Full GC", gch->gc_cause()), PrintGC && !PrintGCDetails, true, NULL); gch->trace_heap_before_gc(_gc_tracer); // When collecting the permanent generation Method*s may be moving, // so we either have to flush all bcp data or convert it into bci. CodeCache::gc_prologue(); Threads::gc_prologue(); // Increment the invocation count _total_invocations++; // Capture heap size before collection for printing. size_t gch_prev_used = gch->used(); // Capture used regions for each generation that will be // subject to collection, so that card table adjustments can // be made intelligently (see clear / invalidate further below). gch->save_used_regions(level); allocate_stacks(); // 階段1~4 // 1. Mark live objects // 2. Calculate new addresses // 3. Update pointers // 4. Move objects to new positions mark_sweep_phase1(level, clear_all_softrefs); mark_sweep_phase2(); // Don't add any more derived pointers during phase3 COMPILER2_PRESENT(assert(DerivedPointerTable::is_active(), "Sanity")); COMPILER2_PRESENT(DerivedPointerTable::set_active(false)); mark_sweep_phase3(level); mark_sweep_phase4(); restore_marks(); // Set saved marks for allocation profiler (and other things? -- dld) // (Should this be in general part?) gch->save_marks(); deallocate_stacks(); // If compaction completely evacuated all generations younger than this // one, then we can clear the card table. Otherwise, we must invalidate // it (consider all cards dirty). In the future, we might consider doing // compaction within generations only, and doing card-table sliding. bool all_empty = true; for (int i = 0; all_empty && i < level; i++) { Generation* g = gch->get_gen(i); all_empty = all_empty && gch->get_gen(i)->used() == 0; } GenRemSet* rs = gch->rem_set(); Generation* old_gen = gch->get_gen(level); // Clear/invalidate below make use of the "prev_used_regions" saved earlier. if (all_empty) { // We've evacuated all generations below us. rs->clear_into_younger(old_gen); } else { // Invalidate the cards corresponding to the currently used // region and clear those corresponding to the evacuated region. rs->invalidate_or_clear(old_gen); } Threads::gc_epilogue(); CodeCache::gc_epilogue(); JvmtiExport::gc_epilogue(); if (PrintGC && !PrintGCDetails) { gch->print_heap_change(gch_prev_used); } // refs processing: clean slate _ref_processor = NULL; // Update heap occupancy information which is used as // input to soft ref clearing policy at the next gc. Universe::update_heap_info_at_gc(); // Update time of last gc for all generations we collected // (which curently is all the generations in the heap). // We need to use a monotonically non-deccreasing time in ms // or we will see time-warp warnings and os::javaTimeMillis() // does not guarantee monotonicity. jlong now = os::javaTimeNanos() / NANOSECS_PER_MILLISEC; gch->update_time_of_last_gc(now); gch->trace_heap_after_gc(_gc_tracer); }
害,太複雜,有空慢慢拆解吧。反正大致就是標記-清除演算法步驟,在必要地方記錄gc資訊並列印,更新上下文資訊。其中,最重要的是幾個 phase, 可展開閱讀。此處就當拋磚引玉了。
// genMarkSweep.cpp void GenMarkSweep::mark_sweep_phase1(int level, bool clear_all_softrefs) { // Recursively traverse all live objects and mark them GCTraceTime tm("phase 1", PrintGC && Verbose, true, _gc_timer); trace(" 1"); GenCollectedHeap* gch = GenCollectedHeap::heap(); // Because follow_root_closure is created statically, cannot // use OopsInGenClosure constructor which takes a generation, // as the Universe has not been created when the static constructors // are run. follow_root_closure.set_orig_generation(gch->get_gen(level)); // Need new claim bits before marking starts. ClassLoaderDataGraph::clear_claimed_marks(); gch->gen_process_strong_roots(level, false, // Younger gens are not roots. true, // activate StrongRootsScope false, // not scavenging SharedHeap::SO_SystemClasses, &follow_root_closure, true, // walk code active on stacks &follow_root_closure, &follow_klass_closure); // Process reference objects found during marking { ref_processor()->setup_policy(clear_all_softrefs); const ReferenceProcessorStats& stats = ref_processor()->process_discovered_references( &is_alive, &keep_alive, &follow_stack_closure, NULL, _gc_timer); gc_tracer()->report_gc_reference_stats(stats); } // This is the point where the entire marking should have completed. assert(_marking_stack.is_empty(), "Marking should have completed"); // Unload classes and purge the SystemDictionary. bool purged_class = SystemDictionary::do_unloading(&is_alive); // Unload nmethods. CodeCache::do_unloading(&is_alive, purged_class); // Prune dead klasses from subklass/sibling/implementor lists. Klass::clean_weak_klass_links(&is_alive); // Delete entries for dead interned strings. StringTable::unlink(&is_alive); // Clean up unreferenced symbols in symbol table. SymbolTable::unlink(); gc_tracer()->report_object_count_after_gc(&is_alive); } void GenMarkSweep::mark_sweep_phase2() { // Now all live objects are marked, compute the new object addresses. // It is imperative that we traverse perm_gen LAST. If dead space is // allowed a range of dead object may get overwritten by a dead int // array. If perm_gen is not traversed last a Klass* may get // overwritten. This is fine since it is dead, but if the class has dead // instances we have to skip them, and in order to find their size we // need the Klass*! // // It is not required that we traverse spaces in the same order in // phase2, phase3 and phase4, but the ValidateMarkSweep live oops // tracking expects us to do so. See comment under phase4. GenCollectedHeap* gch = GenCollectedHeap::heap(); GCTraceTime tm("phase 2", PrintGC && Verbose, true, _gc_timer); trace("2"); gch->prepare_for_compaction(); } void GenMarkSweep::mark_sweep_phase3(int level) { GenCollectedHeap* gch = GenCollectedHeap::heap(); // Adjust the pointers to reflect the new locations GCTraceTime tm("phase 3", PrintGC && Verbose, true, _gc_timer); trace("3"); // Need new claim bits for the pointer adjustment tracing. ClassLoaderDataGraph::clear_claimed_marks(); // Because the closure below is created statically, we cannot // use OopsInGenClosure constructor which takes a generation, // as the Universe has not been created when the static constructors // are run. adjust_pointer_closure.set_orig_generation(gch->get_gen(level)); gch->gen_process_strong_roots(level, false, // Younger gens are not roots. true, // activate StrongRootsScope false, // not scavenging SharedHeap::SO_AllClasses, &adjust_pointer_closure, false, // do not walk code &adjust_pointer_closure, &adjust_klass_closure); // Now adjust pointers in remaining weak roots. (All of which should // have been cleared if they pointed to non-surviving objects.) CodeBlobToOopClosure adjust_code_pointer_closure(&adjust_pointer_closure, /*do_marking=*/ false); gch->gen_process_weak_roots(&adjust_pointer_closure, &adjust_code_pointer_closure); adjust_marks(); GenAdjustPointersClosure blk; gch->generation_iterate(&blk, true); } void GenMarkSweep::mark_sweep_phase4() { // All pointers are now adjusted, move objects accordingly // It is imperative that we traverse perm_gen first in phase4. All // classes must be allocated earlier than their instances, and traversing // perm_gen first makes sure that all Klass*s have moved to their new // location before any instance does a dispatch through it's klass! // The ValidateMarkSweep live oops tracking expects us to traverse spaces // in the same order in phase2, phase3 and phase4. We don't quite do that // here (perm_gen first rather than last), so we tell the validate code // to use a higher index (saved from phase2) when verifying perm_gen. GenCollectedHeap* gch = GenCollectedHeap::heap(); GCTraceTime tm("phase 4", PrintGC && Verbose, true, _gc_timer); trace("4"); GenCompactClosure blk; gch->generation_iterate(&blk, true); }