在這篇中我將講述GC Collector內部的實現, 這是CoreCLR中除了JIT之外最複雜部分,下面一些概念目前還沒有有公開的文檔和書籍講到。html
爲了分析這部分我花了一個多月的時間,期間也屢次向CoreCLR的開發組提問過,我有信心如下內容都是比較準確的,但若是你發現了錯誤或者有疑問的地方請指出來,
如下的內容基於CoreCLR 1.1.0的源代碼分析,之後可能會有所改變。node
由於內容過長,我分紅了兩篇,這一篇分析代碼,下一篇實際使用LLDB跟蹤GC收集垃圾的處理。linux
GC通常在已預留的內存不夠用或者已經分配量超過閾值時觸發,場景包括:c++
當調用try_allocate_more_space不能從segment結尾或自由對象列表獲取新的空間時會觸發GC, 詳細能夠看我上一篇中分析的代碼。git
閾值儲存在各個heap的dd_min_gc_size(初始值), dd_desired_allocation(動態調整值), dd_new_allocation(消耗值)中,每次給分配上下文指定空間時會減小dd_new_allocation。github
若是dd_new_allocation變爲負數或者與dd_desired_allocation的比例小於必定值則觸發GC,
觸發完GC之後會從新調整dd_new_allocation到dd_desired_allocation。web
參考new_allocation_limit, new_allocation_allowed和check_for_full_gc函數。算法
值得一提的是能夠在.Net程序中使用GC.RegisterForFullGCNotification能夠設置觸發GC須要的dd_new_allocation / dd_desired_allocation的比例(會儲存在fgn_maxgen_percent和fgn_loh_percent中), 設置一個大於0的比例可讓GC觸發的更加頻繁。windows
容許手動設置特殊的GC觸發策略, 參考這個文檔數組
做爲例子,你能夠試着在運行程序前運行export COMPlus_GCStress=1
GCStrees會經過調用GCStress<gc_on_alloc>::MaybeTrigger(acontext)
觸發,
若是你設置了COMPlus_GCStressStart環境變量,在調用MaybeTrigger必定次數後會強制觸發GC,另外還有COMPlus_GCStressStartAtJit等參數,請參考上面的文檔。
默認StressGC不會啓用。
在.Net程序中使用GC.Collect能夠觸發手動觸發GC,我相信大家都知道。
調用.Net中的GC.Collect會調用CoreCLR中的GCHeap::GarbageCollect => GarbageCollectTry => GarbageCollectGeneration。
如下函數大部分都在gc.cpp裏,在這個文件裏的函數我就不一一標出文件了。
GC的入口點是GCHeap::GarbageCollectGeneration函數,這個函數的主要做用是中止運行引擎和調用各個gc_heap的gc_heap::garbage_collect函數
由於這一篇重點在於GC作出的處理,我將不對如何中止運行引擎和後臺GC作出詳細的解釋,但願之後能夠再寫一篇文章講述
// 第一個參數是回收垃圾的代, 例如等於1時會回收gen 0和gen 1的垃圾 // 第二個參數是觸發GC的緣由 size_t GCHeap::GarbageCollectGeneration (unsigned int gen, gc_reason reason) { dprintf (2, ("triggered a GC!")); // 獲取gc_heap實例,意義不大 #ifdef MULTIPLE_HEAPS gc_heap* hpt = gc_heap::g_heaps[0]; #else gc_heap* hpt = 0; #endif //MULTIPLE_HEAPS // 獲取當前線程和dd數據 Thread* current_thread = GetThread(); BOOL cooperative_mode = TRUE; dynamic_data* dd = hpt->dynamic_data_of (gen); size_t localCount = dd_collection_count (dd); // 獲取GC鎖, 防止重複觸發GC enter_spin_lock (&gc_heap::gc_lock); dprintf (SPINLOCK_LOG, ("GC Egc")); ASSERT_HOLDING_SPIN_LOCK(&gc_heap::gc_lock); //don't trigger another GC if one was already in progress //while waiting for the lock { size_t col_count = dd_collection_count (dd); if (localCount != col_count) { #ifdef SYNCHRONIZATION_STATS gc_lock_contended++; #endif //SYNCHRONIZATION_STATS dprintf (SPINLOCK_LOG, ("no need GC Lgc")); leave_spin_lock (&gc_heap::gc_lock); // We don't need to release msl here 'cause this means a GC // has happened and would have release all msl's. return col_count; } } // 統計GC的開始時間(包括中止運行引擎使用的時間) #ifdef COUNT_CYCLES int gc_start = GetCycleCount32(); #endif //COUNT_CYCLES #ifdef TRACE_GC #ifdef COUNT_CYCLES AllocDuration += GetCycleCount32() - AllocStart; #else AllocDuration += clock() - AllocStart; #endif //COUNT_CYCLES #endif //TRACE_GC // 設置觸發GC的緣由 gc_heap::g_low_memory_status = (reason == reason_lowmemory) || (reason == reason_lowmemory_blocking) || g_bLowMemoryFromHost; if (g_bLowMemoryFromHost) reason = reason_lowmemory_host; gc_trigger_reason = reason; // 重設GC結束的事件 // 如下說的"事件"的做用和"信號量", .Net中的"Monitor"同樣 #ifdef MULTIPLE_HEAPS for (int i = 0; i < gc_heap::n_heaps; i++) { gc_heap::g_heaps[i]->reset_gc_done(); } #else gc_heap::reset_gc_done(); #endif //MULTIPLE_HEAPS // 標記gc已開始, 全局靜態變量 gc_heap::gc_started = TRUE; // 中止運行引擎 { init_sync_log_stats(); #ifndef MULTIPLE_HEAPS // 讓當前線程進入preemptive模式 // 最終會調用Thread::EnablePreemptiveGC // 設置線程的m_fPreemptiveGCDisabled等於0 cooperative_mode = gc_heap::enable_preemptive (current_thread); dprintf (2, ("Suspending EE")); BEGIN_TIMING(suspend_ee_during_log); // 中止運行引擎,這裏我只作簡單解釋 // - 調用ThreadSuspend::SuspendEE // - 調用LockThreadStore鎖住線程集合直到RestartEE // - 設置GCHeap中全局事件WaitForGCEvent // - 調用ThreadStore::TrapReturingThreads // - 設置全局變量g_TrapReturningThreads,jit會生成檢查這個全局變量的代碼 // - 調用SuspendRuntime, 中止除了當前線程之外的線程,若是線程在cooperative模式則劫持並中止,若是線程在preemptive模式則阻止進入cooperative模式 GCToEEInterface::SuspendEE(GCToEEInterface::SUSPEND_FOR_GC); END_TIMING(suspend_ee_during_log); // 再次判斷是否應該執行gc // 目前若是設置了NoGCRegion(gc_heap::settings.pause_mode == pause_no_gc)則會進一步檢查 // https://msdn.microsoft.com/en-us/library/system.runtime.gclatencymode(v=vs.110).aspx gc_heap::proceed_with_gc_p = gc_heap::should_proceed_with_gc(); // 設置當前線程離開preemptive模式 gc_heap::disable_preemptive (current_thread, cooperative_mode); if (gc_heap::proceed_with_gc_p) pGenGCHeap->settings.init_mechanisms(); else gc_heap::update_collection_counts_for_no_gc(); #endif //!MULTIPLE_HEAPS } // MAP_EVENT_MONITORS(EE_MONITOR_GARBAGE_COLLECTIONS, NotifyEvent(EE_EVENT_TYPE_GC_STARTED, 0)); // 統計GC的開始時間 #ifdef TRACE_GC #ifdef COUNT_CYCLES unsigned start; unsigned finish; start = GetCycleCount32(); #else clock_t start; clock_t finish; start = clock(); #endif //COUNT_CYCLES PromotedObjectCount = 0; #endif //TRACE_GC // 當前收集代的序號 // 後面看到condemned generation時都表示"當前收集代" unsigned int condemned_generation_number = gen; // We want to get a stack from the user thread that triggered the GC // instead of on the GC thread which is the case for Server GC. // But we are doing it for Workstation GC as well to be uniform. FireEtwGCTriggered((int) reason, GetClrInstanceId()); // 進入GC處理 // 若是有多個heap(服務器GC),可使用各個heap的線程並行處理 // 若是隻有一個heap(工做站GC),直接在當前線程處理 #ifdef MULTIPLE_HEAPS GcCondemnedGeneration = condemned_generation_number; // 當前線程進入preemptive模式 cooperative_mode = gc_heap::enable_preemptive (current_thread); BEGIN_TIMING(gc_during_log); // gc_heap::gc_thread_function在收到這個信號之後會進入GC處理 // 在裏面也會判斷proceed_with_gc_p gc_heap::ee_suspend_event.Set(); // 等待全部線程處理完畢 gc_heap::wait_for_gc_done(); END_TIMING(gc_during_log); // 當前線程離開preemptive模式 gc_heap::disable_preemptive (current_thread, cooperative_mode); condemned_generation_number = GcCondemnedGeneration; #else // 在當前線程中進入GC處理 if (gc_heap::proceed_with_gc_p) { BEGIN_TIMING(gc_during_log); pGenGCHeap->garbage_collect (condemned_generation_number); END_TIMING(gc_during_log); } #endif //MULTIPLE_HEAPS // 統計GC的結束時間 #ifdef TRACE_GC #ifdef COUNT_CYCLES finish = GetCycleCount32(); #else finish = clock(); #endif //COUNT_CYCLES GcDuration += finish - start; dprintf (3, ("<GC# %d> Condemned: %d, Duration: %d, total: %d Alloc Avg: %d, Small Objects:%d Large Objects:%d", VolatileLoad(&pGenGCHeap->settings.gc_index), condemned_generation_number, finish - start, GcDuration, AllocCount ? (AllocDuration / AllocCount) : 0, AllocSmallCount, AllocBigCount)); AllocCount = 0; AllocDuration = 0; #endif // TRACE_GC #ifdef BACKGROUND_GC // We are deciding whether we should fire the alloc wait end event here // because in begin_foreground we could be calling end_foreground // if we need to retry. if (gc_heap::alloc_wait_event_p) { hpt->fire_alloc_wait_event_end (awr_fgc_wait_for_bgc); gc_heap::alloc_wait_event_p = FALSE; } #endif //BACKGROUND_GC // 重啓運行引擎 #ifndef MULTIPLE_HEAPS #ifdef BACKGROUND_GC if (!gc_heap::dont_restart_ee_p) { #endif //BACKGROUND_GC BEGIN_TIMING(restart_ee_during_log); // 重啓運行引擎,這裏我只作簡單解釋 // - 調用SetGCDone // - 調用ResumeRuntime // - 調用UnlockThreadStore GCToEEInterface::RestartEE(TRUE); END_TIMING(restart_ee_during_log); #ifdef BACKGROUND_GC } #endif //BACKGROUND_GC #endif //!MULTIPLE_HEAPS #ifdef COUNT_CYCLES printf ("GC: %d Time: %d\n", GcCondemnedGeneration, GetCycleCount32() - gc_start); #endif //COUNT_CYCLES // 設置gc_done_event事件和釋放gc鎖 // 若是有多個heap, 這裏的處理會在gc_thread_function中完成 #ifndef MULTIPLE_HEAPS process_sync_log_stats(); gc_heap::gc_started = FALSE; gc_heap::set_gc_done(); dprintf (SPINLOCK_LOG, ("GC Lgc")); leave_spin_lock (&gc_heap::gc_lock); #endif //!MULTIPLE_HEAPS #ifdef FEATURE_PREMORTEM_FINALIZATION if ((!pGenGCHeap->settings.concurrent && pGenGCHeap->settings.found_finalizers) || FinalizerThread::HaveExtraWorkForFinalizer()) { FinalizerThread::EnableFinalization(); } #endif // FEATURE_PREMORTEM_FINALIZATION return dd_collection_count (dd); }
如下是gc_heap::garbage_collect
函數,這個函數也是GC的入口點函數,
主要做用是針對gc_heap
作gc開始前和結束後的清理工做,例如重設各個線程的分配上下文和修改gc參數
// 第一個參數是回收垃圾的代 int gc_heap::garbage_collect (int n) { // 枚舉線程 // - 統計目前用的分配上下文數量 // - 在分配上下文的alloc_ptr和limit之間建立free object // - 設置全部分配上下文的alloc_ptr和limit到0 //reset the number of alloc contexts alloc_contexts_used = 0; fix_allocation_contexts (TRUE); // 清理在gen 0範圍的brick table // brick table將在下面解釋 #ifdef MULTIPLE_HEAPS clear_gen0_bricks(); #endif //MULTIPLE_HEAPS // 若是開始了NoGCRegion,而且disallowFullBlockingGC等於true,則跳過此次GC // https://msdn.microsoft.com/en-us/library/dn906204(v=vs.110).aspx if ((settings.pause_mode == pause_no_gc) && current_no_gc_region_info.minimal_gc_p) { #ifdef MULTIPLE_HEAPS gc_t_join.join(this, gc_join_minimal_gc); if (gc_t_join.joined()) { #endif //MULTIPLE_HEAPS #ifdef MULTIPLE_HEAPS // this is serialized because we need to get a segment for (int i = 0; i < n_heaps; i++) { if (!(g_heaps[i]->expand_soh_with_minimal_gc())) current_no_gc_region_info.start_status = start_no_gc_no_memory; } #else if (!expand_soh_with_minimal_gc()) current_no_gc_region_info.start_status = start_no_gc_no_memory; #endif //MULTIPLE_HEAPS update_collection_counts_for_no_gc(); #ifdef MULTIPLE_HEAPS gc_t_join.restart(); } #endif //MULTIPLE_HEAPS goto done; } // 清空gc_data_per_heap和fgm_result init_records(); memset (&fgm_result, 0, sizeof (fgm_result)); // 設置收集理由到settings成員中 // settings成員的類型是gc_mechanisms, 裏面的值已在前面初始化過,將會貫穿整個gc過程使用 settings.reason = gc_trigger_reason; verify_pinned_queue_p = FALSE; #if defined(ENABLE_PERF_COUNTERS) || defined(FEATURE_EVENT_TRACE) num_pinned_objects = 0; #endif //ENABLE_PERF_COUNTERS || FEATURE_EVENT_TRACE #ifdef STRESS_HEAP if (settings.reason == reason_gcstress) { settings.reason = reason_induced; settings.stress_induced = TRUE; } #endif // STRESS_HEAP #ifdef MULTIPLE_HEAPS // 根據環境從新決定應該收集的代 // 這裏的處理比較雜,大概包括瞭如下的處理 // - 備份dd_new_allocation到dd_gc_new_allocation // - 必要時修改收集的代, 例如最大代的閾值用完或者須要低延遲的時候 // - 必要時設置settings.promotion = true (啓用對象升代, 例如代0對象gc後變代1) // - 算法是 經過卡片標記的對象 / 經過卡片掃描的對象 < 30% 則啓用對象升代(dt_low_card_table_efficiency_p) // - 這個比例儲存在`generation_skip_ratio`中 // - Card Table將在下面解釋,意義是若是前一代的對象不夠多則須要把後一代的對象升代 //align all heaps on the max generation to condemn dprintf (3, ("Joining for max generation to condemn")); condemned_generation_num = generation_to_condemn (n, &blocking_collection, &elevation_requested, FALSE); gc_t_join.join(this, gc_join_generation_determined); if (gc_t_join.joined()) #endif //MULTIPLE_HEAPS { // 判斷是否要打印更多的除錯信息,除錯用 #ifdef TRACE_GC int gc_count = (int)dd_collection_count (dynamic_data_of (0)); if (gc_count >= g_pConfig->GetGCtraceStart()) trace_gc = 1; if (gc_count >= g_pConfig->GetGCtraceEnd()) trace_gc = 0; #endif //TRACE_GC // 複製(合併)各個heap的card table和brick table到全局 #ifdef MULTIPLE_HEAPS #if !defined(SEG_MAPPING_TABLE) && !defined(FEATURE_BASICFREEZE) // 釋放已刪除的segment索引的節點 //delete old slots from the segment table seg_table->delete_old_slots(); #endif //!SEG_MAPPING_TABLE && !FEATURE_BASICFREEZE for (int i = 0; i < n_heaps; i++) { //copy the card and brick tables if (g_card_table != g_heaps[i]->card_table) { g_heaps[i]->copy_brick_card_table(); } g_heaps[i]->rearrange_large_heap_segments(); if (!recursive_gc_sync::background_running_p()) { g_heaps[i]->rearrange_small_heap_segments(); } } #else //MULTIPLE_HEAPS #ifdef BACKGROUND_GC //delete old slots from the segment table #if !defined(SEG_MAPPING_TABLE) && !defined(FEATURE_BASICFREEZE) // 釋放已刪除的segment索引的節點 seg_table->delete_old_slots(); #endif //!SEG_MAPPING_TABLE && !FEATURE_BASICFREEZE // 刪除空segment rearrange_large_heap_segments(); if (!recursive_gc_sync::background_running_p()) { rearrange_small_heap_segments(); } #endif //BACKGROUND_GC // check for card table growth if (g_card_table != card_table) copy_brick_card_table(); #endif //MULTIPLE_HEAPS // 合併各個heap的elevation_requested和blocking_collection選項 BOOL should_evaluate_elevation = FALSE; BOOL should_do_blocking_collection = FALSE; #ifdef MULTIPLE_HEAPS int gen_max = condemned_generation_num; for (int i = 0; i < n_heaps; i++) { if (gen_max < g_heaps[i]->condemned_generation_num) gen_max = g_heaps[i]->condemned_generation_num; if ((!should_evaluate_elevation) && (g_heaps[i]->elevation_requested)) should_evaluate_elevation = TRUE; if ((!should_do_blocking_collection) && (g_heaps[i]->blocking_collection)) should_do_blocking_collection = TRUE; } settings.condemned_generation = gen_max; //logically continues after GC_PROFILING. #else //MULTIPLE_HEAPS // 單gc_heap(工做站GC)時的處理 // 根據環境從新決定應該收集的代,解釋看上面 settings.condemned_generation = generation_to_condemn (n, &blocking_collection, &elevation_requested, FALSE); should_evaluate_elevation = elevation_requested; should_do_blocking_collection = blocking_collection; #endif //MULTIPLE_HEAPS settings.condemned_generation = joined_generation_to_condemn ( should_evaluate_elevation, settings.condemned_generation, &should_do_blocking_collection STRESS_HEAP_ARG(n) ); STRESS_LOG1(LF_GCROOTS|LF_GC|LF_GCALLOC, LL_INFO10, "condemned generation num: %d\n", settings.condemned_generation); record_gcs_during_no_gc(); // 若是收集代大於1(目前只有2,也就是full gc)則啓用對象升代 if (settings.condemned_generation > 1) settings.promotion = TRUE; #ifdef HEAP_ANALYZE // At this point we've decided what generation is condemned // See if we've been requested to analyze survivors after the mark phase if (AnalyzeSurvivorsRequested(settings.condemned_generation)) { heap_analyze_enabled = TRUE; } #endif // HEAP_ANALYZE // 統計GC性能的處理,這裏不分析 #ifdef GC_PROFILING // If we're tracking GCs, then we need to walk the first generation // before collection to track how many items of each class has been // allocated. UpdateGenerationBounds(); GarbageCollectionStartedCallback(settings.condemned_generation, settings.reason == reason_induced); { BEGIN_PIN_PROFILER(CORProfilerTrackGC()); size_t profiling_context = 0; #ifdef MULTIPLE_HEAPS int hn = 0; for (hn = 0; hn < gc_heap::n_heaps; hn++) { gc_heap* hp = gc_heap::g_heaps [hn]; // When we're walking objects allocated by class, then we don't want to walk the large // object heap because then it would count things that may have been around for a while. hp->walk_heap (&AllocByClassHelper, (void *)&profiling_context, 0, FALSE); } #else // When we're walking objects allocated by class, then we don't want to walk the large // object heap because then it would count things that may have been around for a while. gc_heap::walk_heap (&AllocByClassHelper, (void *)&profiling_context, 0, FALSE); #endif //MULTIPLE_HEAPS // Notify that we've reached the end of the Gen 0 scan g_profControlBlock.pProfInterface->EndAllocByClass(&profiling_context); END_PIN_PROFILER(); } #endif // GC_PROFILING // 後臺GC的處理,這裏不分析 #ifdef BACKGROUND_GC if ((settings.condemned_generation == max_generation) && (recursive_gc_sync::background_running_p())) { //TODO BACKGROUND_GC If we just wait for the end of gc, it won't woork // because we have to collect 0 and 1 properly // in particular, the allocation contexts are gone. // For now, it is simpler to collect max_generation-1 settings.condemned_generation = max_generation - 1; dprintf (GTC_LOG, ("bgc - 1 instead of 2")); } if ((settings.condemned_generation == max_generation) && (should_do_blocking_collection == FALSE) && gc_can_use_concurrent && !temp_disable_concurrent_p && ((settings.pause_mode == pause_interactive) || (settings.pause_mode == pause_sustained_low_latency))) { keep_bgc_threads_p = TRUE; c_write (settings.concurrent, TRUE); } #endif //BACKGROUND_GC // 當前gc的標識序號(會在gc1 => update_collection_counts函數裏面更新) settings.gc_index = (uint32_t)dd_collection_count (dynamic_data_of (0)) + 1; // 通知運行引擎GC開始工做 // 這裏會作出一些處理例如釋放JIT中已刪除的HostCodeHeap的內存 // Call the EE for start of GC work // just one thread for MP GC GCToEEInterface::GcStartWork (settings.condemned_generation, max_generation); // TODO: we could fire an ETW event to say this GC as a concurrent GC but later on due to not being able to // create threads or whatever, this could be a non concurrent GC. Maybe for concurrent GC we should fire // it in do_background_gc and if it failed to be a CGC we fire it in gc1... in other words, this should be // fired in gc1. // 更新一些統計用計數器和數據 do_pre_gc(); // 繼續(喚醒)後臺GC線程 #ifdef MULTIPLE_HEAPS gc_start_event.Reset(); //start all threads on the roots. dprintf(3, ("Starting all gc threads for gc")); gc_t_join.restart(); #endif //MULTIPLE_HEAPS } // 更新統計數據 { int gen_num_for_data = max_generation + 1; for (int i = 0; i <= gen_num_for_data; i++) { gc_data_per_heap.gen_data[i].size_before = generation_size (i); generation* gen = generation_of (i); gc_data_per_heap.gen_data[i].free_list_space_before = generation_free_list_space (gen); gc_data_per_heap.gen_data[i].free_obj_space_before = generation_free_obj_space (gen); } } // 打印出錯信息 descr_generations (TRUE); // descr_card_table(); // 若是不使用Write Barrier而是Write Watch時則須要更新Card Table // 默認windows和linux編譯的CoreCLR都會使用Write Barrier // Write Barrier和Card Table將在下面解釋 #ifdef NO_WRITE_BARRIER fix_card_table(); #endif //NO_WRITE_BARRIER // 檢查gc_heap的狀態,除錯用 #ifdef VERIFY_HEAP if ((g_pConfig->GetHeapVerifyLevel() & EEConfig::HEAPVERIFY_GC) && !(g_pConfig->GetHeapVerifyLevel() & EEConfig::HEAPVERIFY_POST_GC_ONLY)) { verify_heap (TRUE); } if (g_pConfig->GetHeapVerifyLevel() & EEConfig::HEAPVERIFY_BARRIERCHECK) checkGCWriteBarrier(); #endif // VERIFY_HEAP // 調用GC的主函數`gc1` // 後臺GC的處理我在這一篇中將不會解釋,但願之後能夠專門寫一篇解釋後臺GC #ifdef BACKGROUND_GC if (settings.concurrent) { // We need to save the settings because we'll need to restore it after each FGC. assert (settings.condemned_generation == max_generation); settings.compaction = FALSE; saved_bgc_settings = settings; #ifdef MULTIPLE_HEAPS if (heap_number == 0) { for (int i = 0; i < n_heaps; i++) { prepare_bgc_thread (g_heaps[i]); } dprintf (2, ("setting bgc_threads_sync_event")); bgc_threads_sync_event.Set(); } else { bgc_threads_sync_event.Wait(INFINITE, FALSE); dprintf (2, ("bgc_threads_sync_event is signalled")); } #else prepare_bgc_thread(0); #endif //MULTIPLE_HEAPS #ifdef MULTIPLE_HEAPS gc_t_join.join(this, gc_join_start_bgc); if (gc_t_join.joined()) #endif //MULTIPLE_HEAPS { do_concurrent_p = TRUE; do_ephemeral_gc_p = FALSE; #ifdef MULTIPLE_HEAPS dprintf(2, ("Joined to perform a background GC")); for (int i = 0; i < n_heaps; i++) { gc_heap* hp = g_heaps[i]; if (!(hp->bgc_thread) || !hp->commit_mark_array_bgc_init (hp->mark_array)) { do_concurrent_p = FALSE; break; } else { hp->background_saved_lowest_address = hp->lowest_address; hp->background_saved_highest_address = hp->highest_address; } } #else do_concurrent_p = (!!bgc_thread && commit_mark_array_bgc_init (mark_array)); if (do_concurrent_p) { background_saved_lowest_address = lowest_address; background_saved_highest_address = highest_address; } #endif //MULTIPLE_HEAPS if (do_concurrent_p) { #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP SoftwareWriteWatch::EnableForGCHeap(); #endif //FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP #ifdef MULTIPLE_HEAPS for (int i = 0; i < n_heaps; i++) g_heaps[i]->current_bgc_state = bgc_initialized; #else current_bgc_state = bgc_initialized; #endif //MULTIPLE_HEAPS int gen = check_for_ephemeral_alloc(); // always do a gen1 GC before we start BGC. // This is temporary for testing purpose. //int gen = max_generation - 1; dont_restart_ee_p = TRUE; if (gen == -1) { // If we decide to not do a GC before the BGC we need to // restore the gen0 alloc context. #ifdef MULTIPLE_HEAPS for (int i = 0; i < n_heaps; i++) { generation_allocation_pointer (g_heaps[i]->generation_of (0)) = 0; generation_allocation_limit (g_heaps[i]->generation_of (0)) = 0; } #else generation_allocation_pointer (youngest_generation) = 0; generation_allocation_limit (youngest_generation) = 0; #endif //MULTIPLE_HEAPS } else { do_ephemeral_gc_p = TRUE; settings.init_mechanisms(); settings.condemned_generation = gen; settings.gc_index = (size_t)dd_collection_count (dynamic_data_of (0)) + 2; do_pre_gc(); // TODO BACKGROUND_GC need to add the profiling stuff here. dprintf (GTC_LOG, ("doing gen%d before doing a bgc", gen)); } //clear the cards so they don't bleed in gen 1 during collection // shouldn't this always be done at the beginning of any GC? //clear_card_for_addresses ( // generation_allocation_start (generation_of (0)), // heap_segment_allocated (ephemeral_heap_segment)); if (!do_ephemeral_gc_p) { do_background_gc(); } } else { settings.compaction = TRUE; c_write (settings.concurrent, FALSE); } #ifdef MULTIPLE_HEAPS gc_t_join.restart(); #endif //MULTIPLE_HEAPS } if (do_concurrent_p) { // At this point we are sure we'll be starting a BGC, so save its per heap data here. // global data is only calculated at the end of the GC so we don't need to worry about // FGCs overwriting it. memset (&bgc_data_per_heap, 0, sizeof (bgc_data_per_heap)); memcpy (&bgc_data_per_heap, &gc_data_per_heap, sizeof(gc_data_per_heap)); if (do_ephemeral_gc_p) { dprintf (2, ("GC threads running, doing gen%d GC", settings.condemned_generation)); gen_to_condemn_reasons.init(); gen_to_condemn_reasons.set_condition (gen_before_bgc); gc_data_per_heap.gen_to_condemn_reasons.init (&gen_to_condemn_reasons); gc1(); #ifdef MULTIPLE_HEAPS gc_t_join.join(this, gc_join_bgc_after_ephemeral); if (gc_t_join.joined()) #endif //MULTIPLE_HEAPS { #ifdef MULTIPLE_HEAPS do_post_gc(); #endif //MULTIPLE_HEAPS settings = saved_bgc_settings; assert (settings.concurrent); do_background_gc(); #ifdef MULTIPLE_HEAPS gc_t_join.restart(); #endif //MULTIPLE_HEAPS } } } else { dprintf (2, ("couldn't create BGC threads, reverting to doing a blocking GC")); gc1(); } } else #endif //BACKGROUND_GC { gc1(); } #ifndef MULTIPLE_HEAPS allocation_running_time = (size_t)GCToOSInterface::GetLowPrecisionTimeStamp(); allocation_running_amount = dd_new_allocation (dynamic_data_of (0)); fgn_last_alloc = dd_new_allocation (dynamic_data_of (0)); #endif //MULTIPLE_HEAPS done: if (settings.pause_mode == pause_no_gc) allocate_for_no_gc_after_gc(); int gn = settings.condemned_generation; return gn; }
GC的主函數是gc1
,包含了GC中最關鍵的處理,也是這一篇中須要重點講解的部分。
gc1
中的整體流程在BOTR文檔已經有初步的介紹:
mark phase
,標記存活的對象plan phase
,決定要壓縮仍是要清掃relocate phase
和compact phase
sweep phase
在看具體的代碼以前讓咱們一塊兒複習以前講到的Object
的結構
GC使用其中的2個bit來保存標記(marked)
和固定(pinned)
標記(marked)
表示對象是存活的,不該該被收集,儲存在MethodTable指針 & 1中固定(pinned)
表示對象不能被移動(壓縮時不要移動這個對象), 儲存在對象頭 & 0x20000000中mark_phase
中被標記,在plan_phase
中被清除,不會殘留到GC結束後再複習堆段(heap segment)的結構
一個gc_heap中有兩個segment鏈表,一個是小對象(gen 0~gen 2)用的鏈表,一個是大對象(gen 3)用的鏈表,
其中鏈表的最後一個節點是ephemeral heap segment
,只用來保存gen 0和gen 1的對象,各個代都有一個開始地址,在開始地址以後的對象屬於這個代或更年輕的代。
gc_heap::gc1
函數的代碼以下
//internal part of gc used by the serial and concurrent version void gc_heap::gc1() { #ifdef BACKGROUND_GC assert (settings.concurrent == (uint32_t)(bgc_thread_id.IsCurrentThread())); #endif //BACKGROUND_GC // 開始統計各個階段的時間,這些是全局變量 #ifdef TIME_GC mark_time = plan_time = reloc_time = compact_time = sweep_time = 0; #endif //TIME_GC // 驗證小對象的segment列表(gen0~2的segment),除錯用 verify_soh_segment_list(); int n = settings.condemned_generation; // gc的標識序號+1 update_collection_counts (); // 調用mark_phase和plan_phase(包括relocate, compact, sweep) // 後臺GC這一篇不解釋,請跳到#endif //BACKGROUND_GC #ifdef BACKGROUND_GC bgc_alloc_lock->check(); #endif //BACKGROUND_GC // 打印除錯信息 free_list_info (max_generation, "beginning"); // 設置當前收集代 vm_heap->GcCondemnedGeneration = settings.condemned_generation; assert (g_card_table == card_table); { // 設置收集範圍 // 若是收集gen 2則從最小的地址一直到最大的地址 // 不然從收集代的開始地址一直到短暫的堆段(ephemeral heap segment)的預留地址 if (n == max_generation) { gc_low = lowest_address; gc_high = highest_address; } else { gc_low = generation_allocation_start (generation_of (n)); gc_high = heap_segment_reserved (ephemeral_heap_segment); } #ifdef BACKGROUND_GC if (settings.concurrent) { #ifdef TRACE_GC time_bgc_last = GetHighPrecisionTimeStamp(); #endif //TRACE_GC fire_bgc_event (BGCBegin); concurrent_print_time_delta ("BGC"); //#ifdef WRITE_WATCH //reset_write_watch (FALSE); //#endif //WRITE_WATCH concurrent_print_time_delta ("RW"); background_mark_phase(); free_list_info (max_generation, "after mark phase"); background_sweep(); free_list_info (max_generation, "after sweep phase"); } else #endif //BACKGROUND_GC { // 調用mark_phase標記存活的對象 // 請看下面的詳解 mark_phase (n, FALSE); // 設置對象結構有可能不合法,由於plan_phase中可能會對對象作出臨時性的破壞 GCScan::GcRuntimeStructuresValid (FALSE); // 調用plan_phase計劃是否要壓縮仍是清掃 // 這個函數內部會完成壓縮或者清掃,請看下面的詳解 plan_phase (n); // 從新設置對象結構合法 GCScan::GcRuntimeStructuresValid (TRUE); } } // 記錄gc結束時間 size_t end_gc_time = GetHighPrecisionTimeStamp(); // printf ("generation: %d, elapsed time: %Id\n", n, end_gc_time - dd_time_clock (dynamic_data_of (0))); // 調整generation_pinned_allocated(固定對象的大小)和generation_allocation_size(分配的大小) //adjust the allocation size from the pinned quantities. for (int gen_number = 0; gen_number <= min (max_generation,n+1); gen_number++) { generation* gn = generation_of (gen_number); if (settings.compactin) { generation_pinned_allocated (gn) += generation_pinned_allocation_compact_size (gn); generation_allocation_size (generation_of (gen_number)) += generation_pinned_allocation_compact_size (gn); } else { generation_pinned_allocated (gn) += generation_pinned_allocation_sweep_size (gn); generation_allocation_size (generation_of (gen_number)) += generation_pinned_allocation_sweep_size (gn); } generation_pinned_allocation_sweep_size (gn) = 0; generation_pinned_allocation_compact_size (gn) = 0; } // 更新gc_data_per_heap, 和打印除錯信息 #ifdef BACKGROUND_GC if (settings.concurrent) { dynamic_data* dd = dynamic_data_of (n); dd_gc_elapsed_time (dd) = end_gc_time - dd_time_clock (dd); free_list_info (max_generation, "after computing new dynamic data"); gc_history_per_heap* current_gc_data_per_heap = get_gc_data_per_heap(); for (int gen_number = 0; gen_number < max_generation; gen_number++) { dprintf (2, ("end of BGC: gen%d new_alloc: %Id", gen_number, dd_desired_allocation (dynamic_data_of (gen_number)))); current_gc_data_per_heap->gen_data[gen_number].size_after = generation_size (gen_number); current_gc_data_per_heap->gen_data[gen_number].free_list_space_after = generation_free_list_space (generation_of (gen_number)); current_gc_data_per_heap->gen_data[gen_number].free_obj_space_after = generation_free_obj_space (generation_of (gen_number)); } } else #endif //BACKGROUND_GC { free_list_info (max_generation, "end"); for (int gen_number = 0; gen_number <= n; gen_number++) { dynamic_data* dd = dynamic_data_of (gen_number); dd_gc_elapsed_time (dd) = end_gc_time - dd_time_clock (dd); compute_new_dynamic_data (gen_number); } if (n != max_generation) { int gen_num_for_data = ((n < (max_generation - 1)) ? (n + 1) : (max_generation + 1)); for (int gen_number = (n + 1); gen_number <= gen_num_for_data; gen_number++) { get_gc_data_per_heap()->gen_data[gen_number].size_after = generation_size (gen_number); get_gc_data_per_heap()->gen_data[gen_number].free_list_space_after = generation_free_list_space (generation_of (gen_number)); get_gc_data_per_heap()->gen_data[gen_number].free_obj_space_after = generation_free_obj_space (generation_of (gen_number)); } } get_gc_data_per_heap()->maxgen_size_info.running_free_list_efficiency = (uint32_t)(generation_allocator_efficiency (generation_of (max_generation)) * 100); free_list_info (max_generation, "after computing new dynamic data"); if (heap_number == 0) { dprintf (GTC_LOG, ("GC#%d(gen%d) took %Idms", dd_collection_count (dynamic_data_of (0)), settings.condemned_generation, dd_gc_elapsed_time (dynamic_data_of (0)))); } for (int gen_number = 0; gen_number <= (max_generation + 1); gen_number++) { dprintf (2, ("end of FGC/NGC: gen%d new_alloc: %Id", gen_number, dd_desired_allocation (dynamic_data_of (gen_number)))); } } // 更新收集代+1代的動態數據(dd) if (n < max_generation) { compute_promoted_allocation (1 + n); dynamic_data* dd = dynamic_data_of (1 + n); size_t new_fragmentation = generation_free_list_space (generation_of (1 + n)) + generation_free_obj_space (generation_of (1 + n)); #ifdef BACKGROUND_GC if (current_c_gc_state != c_gc_state_planning) #endif //BACKGROUND_GC { if (settings.promotion) { dd_fragmentation (dd) = new_fragmentation; } else { //assert (dd_fragmentation (dd) == new_fragmentation); } } } // 更新ephemeral_low(gen 1的開始的地址)和ephemeral_high(ephemeral_heap_segment的預留地址) #ifdef BACKGROUND_GC if (!settings.concurrent) #endif //BACKGROUND_GC { adjust_ephemeral_limits(!!IsGCThread()); } #ifdef BACKGROUND_GC assert (ephemeral_low == generation_allocation_start (generation_of ( max_generation -1))); assert (ephemeral_high == heap_segment_reserved (ephemeral_heap_segment)); #endif //BACKGROUND_GC // 若是fgn_maxgen_percent有設置而且收集的是代1則檢查是否要收集代2, 不然通知full_gc_end_event事件 if (fgn_maxgen_percent) { if (settings.condemned_generation == (max_generation - 1)) { check_for_full_gc (max_generation - 1, 0); } else if (settings.condemned_generation == max_generation) { if (full_gc_approach_event_set #ifdef MULTIPLE_HEAPS && (heap_number == 0) #endif //MULTIPLE_HEAPS ) { dprintf (2, ("FGN-GC: setting gen2 end event")); full_gc_approach_event.Reset(); #ifdef BACKGROUND_GC // By definition WaitForFullGCComplete only succeeds if it's full, *blocking* GC, otherwise need to return N/A fgn_last_gc_was_concurrent = settings.concurrent ? TRUE : FALSE; #endif //BACKGROUND_GC full_gc_end_event.Set(); full_gc_approach_event_set = false; } } } // 從新決定分配量(allocation_quantum) // 這裏的 dd_new_allocation 已經從新設置過 // 分配量 = 離下次啓動gc須要分配的大小 / (2 * 已用的分配上下文數量), 最小1K, 最大8K // 若是很快就要從新啓動gc, 或者用的分配上下文較多(浪費較多), 則須要減小分配量 // 大部分狀況下這裏的分配量都會設置爲默認的8K #ifdef BACKGROUND_GC if (!settings.concurrent) #endif //BACKGROUND_GC { //decide on the next allocation quantum if (alloc_contexts_used >= 1) { allocation_quantum = Align (min ((size_t)CLR_SIZE, (size_t)max (1024, get_new_allocation (0) / (2 * alloc_contexts_used))), get_alignment_constant(FALSE)); dprintf (3, ("New allocation quantum: %d(0x%Ix)", allocation_quantum, allocation_quantum)); } } // 重設Write Watch,默認會用Write barrier因此這裏不會被調用 #ifdef NO_WRITE_BARRIER reset_write_watch(FALSE); #endif //NO_WRITE_BARRIER // 打印出錯信息 descr_generations (FALSE); descr_card_table(); // 驗證小對象的segment列表(gen0~2的segment),除錯用 verify_soh_segment_list(); #ifdef BACKGROUND_GC add_to_history_per_heap(); if (heap_number == 0) { add_to_history(); } #endif // BACKGROUND_GC #ifdef GC_STATS if (GCStatistics::Enabled() && heap_number == 0) g_GCStatistics.AddGCStats(settings, dd_gc_elapsed_time(dynamic_data_of(settings.condemned_generation))); #endif // GC_STATS #ifdef TIME_GC fprintf (stdout, "%d,%d,%d,%d,%d,%d\n", n, mark_time, plan_time, reloc_time, compact_time, sweep_time); #endif //TIME_GC #ifdef BACKGROUND_GC assert (settings.concurrent == (uint32_t)(bgc_thread_id.IsCurrentThread())); #endif //BACKGROUND_GC // 檢查heap狀態,除錯用 // 若是是後臺gc還須要中止運行引擎,驗證完之後再重啓 #if defined(VERIFY_HEAP) || (defined (FEATURE_EVENT_TRACE) && defined(BACKGROUND_GC)) if (FALSE #ifdef VERIFY_HEAP // Note that right now g_pConfig->GetHeapVerifyLevel always returns the same // value. If we ever allow randomly adjusting this as the process runs, // we cannot call it this way as joins need to match - we must have the same // value for all heaps like we do with bgc_heap_walk_for_etw_p. || (g_pConfig->GetHeapVerifyLevel() & EEConfig::HEAPVERIFY_GC) #endif #if defined(FEATURE_EVENT_TRACE) && defined(BACKGROUND_GC) || (bgc_heap_walk_for_etw_p && settings.concurrent) #endif ) { #ifdef BACKGROUND_GC Thread* current_thread = GetThread(); BOOL cooperative_mode = TRUE; if (settings.concurrent) { cooperative_mode = enable_preemptive (current_thread); #ifdef MULTIPLE_HEAPS bgc_t_join.join(this, gc_join_suspend_ee_verify); if (bgc_t_join.joined()) { bgc_threads_sync_event.Reset(); dprintf(2, ("Joining BGC threads to suspend EE for verify heap")); bgc_t_join.restart(); } if (heap_number == 0) { suspend_EE(); bgc_threads_sync_event.Set(); } else { bgc_threads_sync_event.Wait(INFINITE, FALSE); dprintf (2, ("bgc_threads_sync_event is signalled")); } #else suspend_EE(); #endif //MULTIPLE_HEAPS //fix the allocation area so verify_heap can proceed. fix_allocation_contexts (FALSE); } #endif //BACKGROUND_GC #ifdef BACKGROUND_GC assert (settings.concurrent == (uint32_t)(bgc_thread_id.IsCurrentThread())); #ifdef FEATURE_EVENT_TRACE if (bgc_heap_walk_for_etw_p && settings.concurrent) { make_free_lists_for_profiler_for_bgc(); } #endif // FEATURE_EVENT_TRACE #endif //BACKGROUND_GC #ifdef VERIFY_HEAP if (g_pConfig->GetHeapVerifyLevel() & EEConfig::HEAPVERIFY_GC) verify_heap (FALSE); #endif // VERIFY_HEAP #ifdef BACKGROUND_GC if (settings.concurrent) { repair_allocation_contexts (TRUE); #ifdef MULTIPLE_HEAPS bgc_t_join.join(this, gc_join_restart_ee_verify); if (bgc_t_join.joined()) { bgc_threads_sync_event.Reset(); dprintf(2, ("Joining BGC threads to restart EE after verify heap")); bgc_t_join.restart(); } if (heap_number == 0) { restart_EE(); bgc_threads_sync_event.Set(); } else { bgc_threads_sync_event.Wait(INFINITE, FALSE); dprintf (2, ("bgc_threads_sync_event is signalled")); } #else restart_EE(); #endif //MULTIPLE_HEAPS disable_preemptive (current_thread, cooperative_mode); } #endif //BACKGROUND_GC } #endif // defined(VERIFY_HEAP) || (defined(FEATURE_EVENT_TRACE) && defined(BACKGROUND_GC)) // 若是有多個heap(服務器GC),平均各個heap的閾值(dd_gc_new_allocation, dd_new_allocation, dd_desired_allocation) // 其餘服務器GC和工做站GC的共通處理請跳到#else看 #ifdef MULTIPLE_HEAPS if (!settings.concurrent) { gc_t_join.join(this, gc_join_done); if (gc_t_join.joined ()) { gc_heap::internal_gc_done = false; //equalize the new desired size of the generations int limit = settings.condemned_generation; if (limit == max_generation) { limit = max_generation+1; } for (int gen = 0; gen <= limit; gen++) { size_t total_desired = 0; for (int i = 0; i < gc_heap::n_heaps; i++) { gc_heap* hp = gc_heap::g_heaps[i]; dynamic_data* dd = hp->dynamic_data_of (gen); size_t temp_total_desired = total_desired + dd_desired_allocation (dd); if (temp_total_desired < total_desired) { // we overflowed. total_desired = (size_t)MAX_PTR; break; } total_desired = temp_total_desired; } size_t desired_per_heap = Align (total_desired/gc_heap::n_heaps, get_alignment_constant ((gen != (max_generation+1)))); if (gen == 0) { #if 1 //subsumed by the linear allocation model // to avoid spikes in mem usage due to short terms fluctuations in survivorship, // apply some smoothing. static size_t smoothed_desired_per_heap = 0; size_t smoothing = 3; // exponential smoothing factor if (smoothing > VolatileLoad(&settings.gc_index)) smoothing = VolatileLoad(&settings.gc_index); smoothed_desired_per_heap = desired_per_heap / smoothing + ((smoothed_desired_per_heap / smoothing) * (smoothing-1)); dprintf (1, ("sn = %Id n = %Id", smoothed_desired_per_heap, desired_per_heap)); desired_per_heap = Align(smoothed_desired_per_heap, get_alignment_constant (true)); #endif //0 // if desired_per_heap is close to min_gc_size, trim it // down to min_gc_size to stay in the cache gc_heap* hp = gc_heap::g_heaps[0]; dynamic_data* dd = hp->dynamic_data_of (gen); size_t min_gc_size = dd_min_gc_size(dd); // if min GC size larger than true on die cache, then don't bother // limiting the desired size if ((min_gc_size <= GCToOSInterface::GetLargestOnDieCacheSize(TRUE) / GCToOSInterface::GetLogicalCpuCount()) && desired_per_heap <= 2*min_gc_size) { desired_per_heap = min_gc_size; } #ifdef BIT64 desired_per_heap = joined_youngest_desired (desired_per_heap); dprintf (2, ("final gen0 new_alloc: %Id", desired_per_heap)); #endif // BIT64 gc_data_global.final_youngest_desired = desired_per_heap; } #if 1 //subsumed by the linear allocation model if (gen == (max_generation + 1)) { // to avoid spikes in mem usage due to short terms fluctuations in survivorship, // apply some smoothing. static size_t smoothed_desired_per_heap_loh = 0; size_t smoothing = 3; // exponential smoothing factor size_t loh_count = dd_collection_count (dynamic_data_of (max_generation)); if (smoothing > loh_count) smoothing = loh_count; smoothed_desired_per_heap_loh = desired_per_heap / smoothing + ((smoothed_desired_per_heap_loh / smoothing) * (smoothing-1)); dprintf( 2, ("smoothed_desired_per_heap_loh = %Id desired_per_heap = %Id", smoothed_desired_per_heap_loh, desired_per_heap)); desired_per_heap = Align(smoothed_desired_per_heap_loh, get_alignment_constant (false)); } #endif //0 for (int i = 0; i < gc_heap::n_heaps; i++) { gc_heap* hp = gc_heap::g_heaps[i]; dynamic_data* dd = hp->dynamic_data_of (gen); dd_desired_allocation (dd) = desired_per_heap; dd_gc_new_allocation (dd) = desired_per_heap; dd_new_allocation (dd) = desired_per_heap; if (gen == 0) { hp->fgn_last_alloc = desired_per_heap; } } } #ifdef FEATURE_LOH_COMPACTION BOOL all_heaps_compacted_p = TRUE; #endif //FEATURE_LOH_COMPACTION for (int i = 0; i < gc_heap::n_heaps; i++) { gc_heap* hp = gc_heap::g_heaps[i]; hp->decommit_ephemeral_segment_pages(); hp->rearrange_large_heap_segments(); #ifdef FEATURE_LOH_COMPACTION all_heaps_compacted_p &= hp->loh_compacted_p; #endif //FEATURE_LOH_COMPACTION } #ifdef FEATURE_LOH_COMPACTION check_loh_compact_mode (all_heaps_compacted_p); #endif //FEATURE_LOH_COMPACTION fire_pevents(); gc_t_join.restart(); } alloc_context_count = 0; heap_select::mark_heap (heap_number); } #else // 如下處理服務器GC和工做站共通,你能夠在#else上面找到對應的代碼 // 設置統計數據(最年輕代的gc閾值) gc_data_global.final_youngest_desired = dd_desired_allocation (dynamic_data_of (0)); // 若是大對象的堆(loh)壓縮模式是僅1次(once)且全部heap的loh都壓縮過則重置loh的壓縮模式 check_loh_compact_mode (loh_compacted_p); // 釋放ephemeral segment中未用到的內存(頁) decommit_ephemeral_segment_pages(); // 觸發etw事件,統計用 fire_pevents(); if (!(settings.concurrent)) { // 刪除空的大對象segment rearrange_large_heap_segments(); // 通知運行引擎GC已完成(GcDone, 目前不會作出實質的處理)而且更新一些統計數據 do_post_gc(); } #ifdef BACKGROUND_GC recover_bgc_settings(); #endif //BACKGROUND_GC #endif //MULTIPLE_HEAPS }
接下來咱們將分別分析GC中的五個階段(mark_phase, plan_phase, relocate_phase, compact_phase, sweep_phase)的內部處理
這個階段的做用是找出收集垃圾的範圍(gc_low ~ gc_high)中有哪些對象是存活的,若是存活則標記(m_pMethTab |= 1),
另外還會根據GC Handle查找有哪些對象是固定的(pinned),若是對象固定則標記(m_uSyncBlockValue |= 0x20000000)。
簡單解釋下GC Handle和Pinned Object,GC Handle用於在託管代碼中調用非託管代碼時能夠決定傳遞的指針的處理,
一個類型是Pinned的GC Handle能夠防止GC在壓縮時移動對象,這樣非託管代碼中保存的指針地址不會失效,詳細能夠看微軟的文檔。
在繼續看代碼以前咱們先來了解Card Table的概念:
若是你以前已經瞭解過GC,可能知道有的語言實現GC會有一個根對象,從根對象一直掃描下去能夠找到全部存活的對象。
但這樣有一個缺陷,若是對象不少,掃描的時間也會相應的變長,爲了提升效率,CoreCLR使用了分代GC(包括以前的.Net Framework都是分代GC),
分代GC能夠只選擇掃描一部分的對象(年輕的對象更有可能被回收)而不是所有對象,那麼分代GC的掃描是如何實現的?
在CoreCLR中對象之間的引用(例如B是A的成員或者B在數組A中,能夠稱做A引用B)通常包含如下狀況
請考慮下圖的狀況,咱們此次只想掃描gen 0,棧中的對象A引用了gen 1的對象B,對象B引用了gen 0的對象C,
在掃描的時候由於B不在掃描範圍(gc_low ~ gc_high)中,CoreCLR不會去繼續跟蹤B的引用,
若是這時候gen 0中無其餘對象引用對象C,是否會致使對象C被誤回收?
爲了解決這種狀況致使的問題,CoreCLR使用了Card Table,所謂Card Table就是專門記錄跨代引用的一個數組
當咱們設置B.member = C
的時候,JIT會把賦值替換爲JIT_WriteBarrier(&B.member, C)
(或同等的其餘函數)
JIT_WriteBarrier
函數中會設置*dst = ref
,而且若是ref
在ephemeral heap segment
中(ref多是gen 0或gen 1的對象)時,
設置dst
在Card Table中所屬的字節爲0xff
,Card Table中一個字節默認涵蓋的範圍在32位下是1024字節,在64位下是2048字節。
須要注意的是這裏的dst
是B.member
的地址而不是B
的地址,B.member
的地址會是B
的地址加必定的偏移值,
而B
自身的地址不必定會在Card Table中獲得標記,咱們以後能夠根據B.member
的地址獲得B
的地址(能夠看find_first_object
函數)。
有了Card Table之後,只回收年輕代(非Full GC)時除了掃描根對象之外咱們還須要掃描Card Table中標記的範圍來防止誤回收對象。
JIT_WriteBarrier
函數的代碼以下
// This function is a JIT helper, but it must NOT use HCIMPL2 because it // modifies Thread state that will not be restored if an exception occurs // inside of memset. A normal EH unwind will not occur. extern "C" HCIMPL2_RAW(VOID, JIT_WriteBarrier, Object **dst, Object *ref) { // Must use static contract here, because if an AV occurs, a normal EH // unwind will not occur, and destructors will not run. STATIC_CONTRACT_MODE_COOPERATIVE; STATIC_CONTRACT_THROWS; STATIC_CONTRACT_GC_NOTRIGGER; #ifdef FEATURE_COUNT_GC_WRITE_BARRIERS IncUncheckedBarrierCount(); #endif // no HELPER_METHOD_FRAME because we are MODE_COOPERATIVE, GC_NOTRIGGER *dst = ref; // If the store above succeeded, "dst" should be in the heap. assert(GCHeap::GetGCHeap()->IsHeapPointer((void*)dst)); #ifdef WRITE_BARRIER_CHECK updateGCShadow(dst, ref); // support debugging write barrier #endif #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP if (SoftwareWriteWatch::IsEnabledForGCHeap()) { SoftwareWriteWatch::SetDirty(dst, sizeof(*dst)); } #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP #ifdef FEATURE_COUNT_GC_WRITE_BARRIERS if((BYTE*) dst >= g_ephemeral_low && (BYTE*) dst < g_ephemeral_high) { UncheckedDestInEphem++; } #endif if((BYTE*) ref >= g_ephemeral_low && (BYTE*) ref < g_ephemeral_high) { #ifdef FEATURE_COUNT_GC_WRITE_BARRIERS UncheckedAfterRefInEphemFilter++; #endif BYTE* pCardByte = (BYTE *)VolatileLoadWithoutBarrier(&g_card_table) + card_byte((BYTE *)dst); if(*pCardByte != 0xFF) { #ifdef FEATURE_COUNT_GC_WRITE_BARRIERS UncheckedAfterAlreadyDirtyFilter++; #endif *pCardByte = 0xFF; } } } HCIMPLEND_RAW
card_byte
macro的代碼以下
#if defined(_WIN64) // Card byte shift is different on 64bit. #define card_byte_shift 11 #else #define card_byte_shift 10 #endif #define card_byte(addr) (((size_t)(addr)) >> card_byte_shift) #define card_bit(addr) (1 << ((((size_t)(addr)) >> (card_byte_shift - 3)) & 7))
gc_heap::mark_phase
函數的代碼以下:
void gc_heap::mark_phase (int condemned_gen_number, BOOL mark_only_p) { assert (settings.concurrent == FALSE); // 掃描上下文 ScanContext sc; sc.thread_number = heap_number; sc.promotion = TRUE; sc.concurrent = FALSE; dprintf(2,("---- Mark Phase condemning %d ----", condemned_gen_number)); // 是否Full GC BOOL full_p = (condemned_gen_number == max_generation); // 統計標記階段的開始時間 #ifdef TIME_GC unsigned start; unsigned finish; start = GetCycleCount32(); #endif //TIME_GC // 重置動態數據(dd) int gen_to_init = condemned_gen_number; if (condemned_gen_number == max_generation) { gen_to_init = max_generation + 1; } for (int gen_idx = 0; gen_idx <= gen_to_init; gen_idx++) { dynamic_data* dd = dynamic_data_of (gen_idx); dd_begin_data_size (dd) = generation_size (gen_idx) - dd_fragmentation (dd) - Align (size (generation_allocation_start (generation_of (gen_idx)))); dprintf (2, ("begin data size for gen%d is %Id", gen_idx, dd_begin_data_size (dd))); dd_survived_size (dd) = 0; dd_pinned_survived_size (dd) = 0; dd_artificial_pinned_survived_size (dd) = 0; dd_added_pinned_size (dd) = 0; #ifdef SHORT_PLUGS dd_padding_size (dd) = 0; #endif //SHORT_PLUGS #if defined (RESPECT_LARGE_ALIGNMENT) || defined (FEATURE_STRUCTALIGN) dd_num_npinned_plugs (dd) = 0; #endif //RESPECT_LARGE_ALIGNMENT || FEATURE_STRUCTALIGN } #ifdef FFIND_OBJECT if (gen0_must_clear_bricks > 0) gen0_must_clear_bricks--; #endif //FFIND_OBJECT size_t last_promoted_bytes = 0; // 重設mark stack // mark_stack_array在GC各個階段有不一樣的用途,在mark phase中的用途是用來標記對象時代替遞歸防止爆棧 promoted_bytes (heap_number) = 0; reset_mark_stack(); #ifdef SNOOP_STATS memset (&snoop_stat, 0, sizeof(snoop_stat)); snoop_stat.heap_index = heap_number; #endif //SNOOP_STATS // 啓用scable marking時 // 服務器GC上會啓用,工做站GC上不會啓用 // scable marking這篇中不會分析 #ifdef MH_SC_MARK if (full_p) { //initialize the mark stack for (int i = 0; i < max_snoop_level; i++) { ((uint8_t**)(mark_stack_array))[i] = 0; } mark_stack_busy() = 1; } #endif //MH_SC_MARK static uint32_t num_sizedrefs = 0; // scable marking的處理 #ifdef MH_SC_MARK static BOOL do_mark_steal_p = FALSE; #endif //MH_SC_MARK #ifdef MULTIPLE_HEAPS gc_t_join.join(this, gc_join_begin_mark_phase); if (gc_t_join.joined()) { #endif //MULTIPLE_HEAPS num_sizedrefs = SystemDomain::System()->GetTotalNumSizedRefHandles(); #ifdef MULTIPLE_HEAPS // scable marking的處理 #ifdef MH_SC_MARK if (full_p) { size_t total_heap_size = get_total_heap_size(); if (total_heap_size > (100 * 1024 * 1024)) { do_mark_steal_p = TRUE; } else { do_mark_steal_p = FALSE; } } else { do_mark_steal_p = FALSE; } #endif //MH_SC_MARK gc_t_join.restart(); } #endif //MULTIPLE_HEAPS { // 初始化mark list, full gc時不會使用 #ifdef MARK_LIST //set up the mark lists from g_mark_list assert (g_mark_list); #ifdef MULTIPLE_HEAPS mark_list = &g_mark_list [heap_number*mark_list_size]; #else mark_list = g_mark_list; #endif //MULTIPLE_HEAPS //dont use the mark list for full gc //because multiple segments are more complex to handle and the list //is likely to overflow if (condemned_gen_number != max_generation) mark_list_end = &mark_list [mark_list_size-1]; else mark_list_end = &mark_list [0]; mark_list_index = &mark_list [0]; #endif //MARK_LIST shigh = (uint8_t*) 0; slow = MAX_PTR; //%type% category = quote (mark); // 若是當前是Full GC而且有類型是SizedRef的GC Handle時把它們做爲根對象掃描 // 參考https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/objecthandle.h#L177 // SizedRef是一個非公開類型的GC Handle(其餘還有RefCounted),目前還看不到有代碼使用 if ((condemned_gen_number == max_generation) && (num_sizedrefs > 0)) { GCScan::GcScanSizedRefs(GCHeap::Promote, condemned_gen_number, max_generation, &sc); fire_mark_event (heap_number, ETW::GC_ROOT_SIZEDREF, (promoted_bytes (heap_number) - last_promoted_bytes)); last_promoted_bytes = promoted_bytes (heap_number); #ifdef MULTIPLE_HEAPS gc_t_join.join(this, gc_join_scan_sizedref_done); if (gc_t_join.joined()) { dprintf(3, ("Done with marking all sized refs. Starting all gc thread for marking other strong roots")); gc_t_join.restart(); } #endif //MULTIPLE_HEAPS } dprintf(3,("Marking Roots")); // 掃描根對象(各個線程中棧和寄存器中的對象) // 這裏的GcScanRoots是一個高階函數,會掃描根對象和根對象引用的對象,並對它們調用傳入的`GCHeap::Promote`函數 // 在下面的relocate phase還會傳入`GCHeap::Relocate`給`GcScanRoots` // BOTR中有一份專門的文檔介紹瞭如何實現棧掃描,地址是 // https://github.com/dotnet/coreclr/blob/master/Documentation/botr/stackwalking.md // 這個函數的內部處理要貼代碼的話會很是的長,這裏我只貼調用流程 // GcScanRoots的處理 // 枚舉線程 // 調用 ScanStackRoots(pThread, fn, sc); // 調用 pThread->StackWalkFrames // 調用 StackWalkFramesEx // 使用 StackFrameIterator 枚舉棧中的全部幀 // 調用 StackFrameIterator::Next // 調用 StackFrameIterator::Filter // 調用 MakeStackwalkerCallback 處理單幀 // 調用 GcStackCrawlCallBack // 若是 IsFrameless 則調用 EECodeManager::EnumGcRefs // 調用 GcInfoDecoder::EnumerateLiveSlots // 調用 GcInfoDecoder::ReportSlotToGC // 若是是寄存器中的對象則調用 GcInfoDecoder::ReportRegisterToGC // 若是是棧上的對象則調用 GcInfoDecoder::ReportStackSlotToGC // 調用 GcEnumObject // 調用 GCHeap::Promote, 接下來和下面的同樣 // 若是 !IsFrameless 則調用 FrameBase::GcScanRoots // 繼承函數的處理 GCFrame::GcScanRoots // 調用 GCHeap::Promote // 調用 gc_heap::mark_object_simple // 調用 gc_mark1, 第一次標記時會返回true // 調用 CObjectHeader::IsMarked !!(((size_t)RawGetMethodTable()) & GC_MARKED) // 調用 CObjectHeader::SetMarked RawSetMethodTable((MethodTable *) (((size_t) RawGetMethodTable()) | GC_MARKED)); // 若是對象未被標記過,調用 go_through_object_cl (macro) 枚舉對象的全部成員 // 對成員對象調用mark_object_simple1,和mark_object_simple的區別是,mark_object_simple1使用mark_stack_array來循環標記對象 // 使用mark_stack_array代替遞歸能夠防止爆棧 // 注意mark_stack_array也有大小限制,若是超過了(overflow)不會擴展(grow),而是記錄並交給下面的GcDhInitialScan處理 GCScan::GcScanRoots(GCHeap::Promote, condemned_gen_number, max_generation, &sc); // 調用通知事件通知有多少字節在這一次被標記 fire_mark_event (heap_number, ETW::GC_ROOT_STACK, (promoted_bytes (heap_number) - last_promoted_bytes)); last_promoted_bytes = promoted_bytes (heap_number); #ifdef BACKGROUND_GC if (recursive_gc_sync::background_running_p()) { scan_background_roots (GCHeap::Promote, heap_number, &sc); } #endif //BACKGROUND_GC // 掃描當前關鍵析構(Critical Finalizer)隊列中對象的引用 // 非關鍵析構隊列中的對象會在下面的ScanForFinalization中掃描 // 關於析構隊列能夠參考這些URL // https://github.com/dotnet/coreclr/blob/master/Documentation/botr/threading.md // http://stackoverflow.com/questions/1268525/what-are-the-finalizer-queue-and-controlthreadmethodentry // http://stackoverflow.com/questions/9030126/why-classes-with-finalizers-need-more-than-one-garbage-collection-cycle // https://msdn.microsoft.com/en-us/library/system.runtime.constrainedexecution.criticalfinalizerobject(v=vs.110).aspx // https://msdn.microsoft.com/en-us/library/system.runtime.constrainedexecution(v=vs.110).aspx #ifdef FEATURE_PREMORTEM_FINALIZATION dprintf(3, ("Marking finalization data")); finalize_queue->GcScanRoots(GCHeap::Promote, heap_number, 0); #endif // FEATURE_PREMORTEM_FINALIZATION // 調用通知事件通知有多少字節在這一次被標記 fire_mark_event (heap_number, ETW::GC_ROOT_FQ, (promoted_bytes (heap_number) - last_promoted_bytes)); last_promoted_bytes = promoted_bytes (heap_number); // MTHTS { // 掃描GC Handle引用的對象 // 若是GC Handle的類型是Pinned同時會設置對象爲pinned // 設置對象爲pinned的流程以下 // GCScan::GcScanHandles // Ref_TracePinningRoots // HndScanHandlersForGC // TableScanHandles // SegmentScanByTypeMap // BlockScanBlocksEphemeral // BlockScanBlocksEphemeralWorker // ScanConsecutiveHandlesWithoutUserData // PinObject // GCHeap::Promote(pRef, (ScanContext *)lpl, GC_CALL_PINNED) // 判斷flags包含GC_CALL_PINNED時調用 gc_heap::pin_object // 若是對象在掃描範圍(gc_low ~ gc_high)時調用set_pinned(o) // GetHeader()->SetGCBit() // m_uSyncBlockValue |= BIT_SBLK_GC_RESERVE // 這裏會標記包括來源於靜態字段的引用 dprintf(3,("Marking handle table")); GCScan::GcScanHandles(GCHeap::Promote, condemned_gen_number, max_generation, &sc); // 調用通知事件通知有多少字節在這一次被標記 fire_mark_event (heap_number, ETW::GC_ROOT_HANDLES, (promoted_bytes (heap_number) - last_promoted_bytes)); last_promoted_bytes = promoted_bytes (heap_number); } // 掃描根對象完成了,若是不是Full GC接下來還須要掃描Card Table // 記錄掃描Card Table以前標記的字節數量(存活的字節數量) #ifdef TRACE_GC size_t promoted_before_cards = promoted_bytes (heap_number); #endif //TRACE_GC // Full GC不須要掃Card Table dprintf (3, ("before cards: %Id", promoted_before_cards)); if (!full_p) { #ifdef CARD_BUNDLE #ifdef MULTIPLE_HEAPS if (gc_t_join.r_join(this, gc_r_join_update_card_bundle)) { #endif //MULTIPLE_HEAPS // 從Write Watch更新Card Table的索引(Card Bundles) // 當內存空間過大時,掃描Card Table的效率會變低,使用Card Bundle能夠標記Card Table中的哪些區域須要掃描 // 在做者環境的下Card Bundle不啓用 update_card_table_bundle (); #ifdef MULTIPLE_HEAPS gc_t_join.r_restart(); } #endif //MULTIPLE_HEAPS #endif //CARD_BUNDLE // 標記對象的函數,須要分析時使用特殊的函數 card_fn mark_object_fn = &gc_heap::mark_object_simple; #ifdef HEAP_ANALYZE heap_analyze_success = TRUE; if (heap_analyze_enabled) { internal_root_array_index = 0; current_obj = 0; current_obj_size = 0; mark_object_fn = &gc_heap::ha_mark_object_simple; } #endif //HEAP_ANALYZE // 遍歷Card Table標記小對象 // 像以前所說的Card Table中對應的區域包含的是成員的地址,不必定包含來源對象的開始地址,find_first_object函數能夠支持找到來源對象的開始地址 // 這個函數除了調用mark_object_simple標記找到的對象之外,還會更新`generation_skip_ratio`這個成員,算法以下 // n_gen 經過卡片標記的對象數量, gc_low ~ gc_high // n_eph 經過卡片掃描的對象數量, 上一代的開始地址 ~ gc_high (cg_pointers_found的累加) // 表示掃描的對象中有多少%的對象被標記了 // generation_skip_ratio = (n_eph > 400) ? (n_gen * 1.0 / n_eph * 100) : 100 // `generation_skip_ratio`會影響到對象是否升代,請搜索上面關於`generation_skip_ratio`的註釋 dprintf(3,("Marking cross generation pointers")); mark_through_cards_for_segments (mark_object_fn, FALSE); // 遍歷Card Table標記大對象 // 處理和前面同樣,只是掃描的範圍是大對象的segment // 這裏也會算出generation_skip_ratio,若是算出的generation_skip_ratio比原來的generation_skip_ratio要小則使用算出的值 dprintf(3,("Marking cross generation pointers for large objects")); mark_through_cards_for_large_objects (mark_object_fn, FALSE); // 調用通知事件通知有多少字節在這一次被標記 dprintf (3, ("marked by cards: %Id", (promoted_bytes (heap_number) - promoted_before_cards))); fire_mark_event (heap_number, ETW::GC_ROOT_OLDER, (promoted_bytes (heap_number) - last_promoted_bytes)); last_promoted_bytes = promoted_bytes (heap_number); } } // scable marking的處理 #ifdef MH_SC_MARK if (do_mark_steal_p) { mark_steal(); } #endif //MH_SC_MARK // 處理HNDTYPE_DEPENDENT類型的GC Handle // 這個GC Handle的意義是保存兩個對象primary和secondary,告訴primary引用了secondary // 若是primary已標記則secondary也會被標記 // 這裏還會處理以前發生的mark_stack_array溢出(循環標記對象時子對象過多致使mark_stack_array容不下) // 此次不必定會完成,下面還會等待線程同步後(服務器GC下)再掃一遍 // Dependent handles need to be scanned with a special algorithm (see the header comment on // scan_dependent_handles for more detail). We perform an initial scan without synchronizing with other // worker threads or processing any mark stack overflow. This is not guaranteed to complete the operation // but in a common case (where there are no dependent handles that are due to be collected) it allows us // to optimize away further scans. The call to scan_dependent_handles is what will cycle through more // iterations if required and will also perform processing of any mark stack overflow once the dependent // handle table has been fully promoted. GCScan::GcDhInitialScan(GCHeap::Promote, condemned_gen_number, max_generation, &sc); scan_dependent_handles(condemned_gen_number, &sc, true); // 通知標記階段完成掃描根對象(和Card Table) #ifdef MULTIPLE_HEAPS dprintf(3, ("Joining for short weak handle scan")); gc_t_join.join(this, gc_join_null_dead_short_weak); if (gc_t_join.joined()) #endif //MULTIPLE_HEAPS { #ifdef HEAP_ANALYZE heap_analyze_enabled = FALSE; DACNotifyGcMarkEnd(condemned_gen_number); #endif // HEAP_ANALYZE GCToEEInterface::AfterGcScanRoots (condemned_gen_number, max_generation, &sc); #ifdef MULTIPLE_HEAPS if (!full_p) { // we used r_join and need to reinitialize states for it here. gc_t_join.r_init(); } //start all threads on the roots. dprintf(3, ("Starting all gc thread for short weak handle scan")); gc_t_join.restart(); #endif //MULTIPLE_HEAPS } // 處理HNDTYPE_WEAK_SHORT類型的GC Handle // 設置未被標記的對象的弱引用(Weak Reference)爲null // 這裏傳的GCHeap::Promote參數不會被用到 // 下面掃描完非關鍵析構隊列還會掃描HNDTYPE_WEAK_LONG類型的GC Handle,請看下面的註釋 // null out the target of short weakref that were not promoted. GCScan::GcShortWeakPtrScan(GCHeap::Promote, condemned_gen_number, max_generation,&sc); // MTHTS: keep by single thread #ifdef MULTIPLE_HEAPS dprintf(3, ("Joining for finalization")); gc_t_join.join(this, gc_join_scan_finalization); if (gc_t_join.joined()) #endif //MULTIPLE_HEAPS { #ifdef MULTIPLE_HEAPS //start all threads on the roots. dprintf(3, ("Starting all gc thread for Finalization")); gc_t_join.restart(); #endif //MULTIPLE_HEAPS } //Handle finalization. size_t promoted_bytes_live = promoted_bytes (heap_number); // 掃描當前非關鍵析構隊列中對象的引用 #ifdef FEATURE_PREMORTEM_FINALIZATION dprintf (3, ("Finalize marking")); finalize_queue->ScanForFinalization (GCHeap::Promote, condemned_gen_number, mark_only_p, __this); #ifdef GC_PROFILING if (CORProfilerTrackGC()) { finalize_queue->WalkFReachableObjects (__this); } #endif //GC_PROFILING #endif // FEATURE_PREMORTEM_FINALIZATION // 再掃一遍HNDTYPE_DEPENDENT類型的GC Handle // Scan dependent handles again to promote any secondaries associated with primaries that were promoted // for finalization. As before scan_dependent_handles will also process any mark stack overflow. scan_dependent_handles(condemned_gen_number, &sc, false); #ifdef MULTIPLE_HEAPS dprintf(3, ("Joining for weak pointer deletion")); gc_t_join.join(this, gc_join_null_dead_long_weak); if (gc_t_join.joined()) { //start all threads on the roots. dprintf(3, ("Starting all gc thread for weak pointer deletion")); gc_t_join.restart(); } #endif //MULTIPLE_HEAPS // 處理HNDTYPE_WEAK_LONG或HNDTYPE_REFCOUNTED類型的GC Handle // 設置未被標記的對象的弱引用(Weak Reference)爲null // 這裏傳的GCHeap::Promote參數不會被用到 // HNDTYPE_WEAK_LONG和HNDTYPE_WEAK_SHORT的區別是,HNDTYPE_WEAK_SHORT會忽略從非關鍵析構隊列的引用而HNDTYPE_WEAK_LONG不會 // null out the target of long weakref that were not promoted. GCScan::GcWeakPtrScan (GCHeap::Promote, condemned_gen_number, max_generation, &sc); // 若是使用了mark list而且並行化(服務器GC下)這裏會進行排序(若是定義了PARALLEL_MARK_LIST_SORT) // MTHTS: keep by single thread #ifdef MULTIPLE_HEAPS #ifdef MARK_LIST #ifdef PARALLEL_MARK_LIST_SORT // unsigned long start = GetCycleCount32(); sort_mark_list(); // printf("sort_mark_list took %u cycles\n", GetCycleCount32() - start); #endif //PARALLEL_MARK_LIST_SORT #endif //MARK_LIST dprintf (3, ("Joining for sync block cache entry scanning")); gc_t_join.join(this, gc_join_null_dead_syncblk); if (gc_t_join.joined()) #endif //MULTIPLE_HEAPS { // 刪除再也不使用的同步索引塊,而且設置對應對象的索引值爲0 // scan for deleted entries in the syncblk cache GCScan::GcWeakPtrScanBySingleThread (condemned_gen_number, max_generation, &sc); #ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING if (g_fEnableARM) { size_t promoted_all_heaps = 0; #ifdef MULTIPLE_HEAPS for (int i = 0; i < n_heaps; i++) { promoted_all_heaps += promoted_bytes (i); } #else promoted_all_heaps = promoted_bytes (heap_number); #endif //MULTIPLE_HEAPS // 記錄此次標記(存活)的字節數 SystemDomain::RecordTotalSurvivedBytes (promoted_all_heaps); } #endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING #ifdef MULTIPLE_HEAPS // 如下是服務器GC下的處理 // 若是使用了mark list而且並行化(服務器GC下)這裏會進行壓縮並排序(若是不定義PARALLEL_MARK_LIST_SORT) #ifdef MARK_LIST #ifndef PARALLEL_MARK_LIST_SORT //compact g_mark_list and sort it. combine_mark_lists(); #endif //PARALLEL_MARK_LIST_SORT #endif //MARK_LIST // 若是以前未決定要升代,這裏再給一次機會判斷是否要升代 // 算法分析 // dd_min_gc_size是每分配多少byte的對象就觸發gc的閾值 // 第0代1倍, 第1代2倍, 再乘以0.1合計 // dd = 上一代的動態數據 // older_gen_size = 上次gc後的對象大小合計 + 從上次gc以來一共新分配了多少byte // 若是m > 上一代的大小, 或者本次標記的對象大小 > m則啓用升代 // 意義是若是上一代太小,或者此次標記(存活)的對象過多則須要升代 //decide on promotion if (!settings.promotion) { size_t m = 0; for (int n = 0; n <= condemned_gen_number;n++) { m += (size_t)(dd_min_gc_size (dynamic_data_of (n))*(n+1)*0.1); } for (int i = 0; i < n_heaps; i++) { dynamic_data* dd = g_heaps[i]->dynamic_data_of (min (condemned_gen_number +1, max_generation)); size_t older_gen_size = (dd_current_size (dd) + (dd_desired_allocation (dd) - dd_new_allocation (dd))); if ((m > (older_gen_size)) || (promoted_bytes (i) > m)) { settings.promotion = TRUE; } } } // scable marking的處理 #ifdef SNOOP_STATS if (do_mark_steal_p) { size_t objects_checked_count = 0; size_t zero_ref_count = 0; size_t objects_marked_count = 0; size_t check_level_count = 0; size_t busy_count = 0; size_t interlocked_count = 0; size_t partial_mark_parent_count = 0; size_t stolen_or_pm_count = 0; size_t stolen_entry_count = 0; size_t pm_not_ready_count = 0; size_t normal_count = 0; size_t stack_bottom_clear_count = 0; for (int i = 0; i < n_heaps; i++) { gc_heap* hp = g_heaps[i]; hp->print_snoop_stat(); objects_checked_count += hp->snoop_stat.objects_checked_count; zero_ref_count += hp->snoop_stat.zero_ref_count; objects_marked_count += hp->snoop_stat.objects_marked_count; check_level_count += hp->snoop_stat.check_level_count; busy_count += hp->snoop_stat.busy_count; interlocked_count += hp->snoop_stat.interlocked_count; partial_mark_parent_count += hp->snoop_stat.partial_mark_parent_count; stolen_or_pm_count += hp->snoop_stat.stolen_or_pm_count; stolen_entry_count += hp->snoop_stat.stolen_entry_count; pm_not_ready_count += hp->snoop_stat.pm_not_ready_count; normal_count += hp->snoop_stat.normal_count; stack_bottom_clear_count += hp->snoop_stat.stack_bottom_clear_count; } fflush (stdout); printf ("-------total stats-------\n"); printf ("%8s | %8s | %8s | %8s | %8s | %8s | %8s | %8s | %8s | %8s | %8s | %8s\n", "checked", "zero", "marked", "level", "busy", "xchg", "pmparent", "s_pm", "stolen", "nready", "normal", "clear"); printf ("%8d | %8d | %8d | %8d | %8d | %8d | %8d | %8d | %8d | %8d | %8d | %8d\n", objects_checked_count, zero_ref_count, objects_marked_count, check_level_count, busy_count, interlocked_count, partial_mark_parent_count, stolen_or_pm_count, stolen_entry_count, pm_not_ready_count, normal_count, stack_bottom_clear_count); } #endif //SNOOP_STATS //start all threads. dprintf(3, ("Starting all threads for end of mark phase")); gc_t_join.restart(); #else //MULTIPLE_HEAPS // 如下是工做站GC下的處理 // 若是以前未決定要升代,這裏再給一次機會判斷是否要升代 // 算法和前面同樣,可是不是乘以0.1而是乘以0.06 //decide on promotion if (!settings.promotion) { size_t m = 0; for (int n = 0; n <= condemned_gen_number;n++) { m += (size_t)(dd_min_gc_size (dynamic_data_of (n))*(n+1)*0.06); } dynamic_data* dd = dynamic_data_of (min (condemned_gen_number +1, max_generation)); size_t older_gen_size = (dd_current_size (dd) + (dd_desired_allocation (dd) - dd_new_allocation (dd))); dprintf (2, ("promotion threshold: %Id, promoted bytes: %Id size n+1: %Id", m, promoted_bytes (heap_number), older_gen_size)); if ((m > older_gen_size) || (promoted_bytes (heap_number) > m)) { settings.promotion = TRUE; } } #endif //MULTIPLE_HEAPS } // 若是使用了mark list而且並行化(服務器GC下)這裏會進行歸併(若是定義了PARALLEL_MARK_LIST_SORT) #ifdef MULTIPLE_HEAPS #ifdef MARK_LIST #ifdef PARALLEL_MARK_LIST_SORT // start = GetCycleCount32(); merge_mark_lists(); // printf("merge_mark_lists took %u cycles\n", GetCycleCount32() - start); #endif //PARALLEL_MARK_LIST_SORT #endif //MARK_LIST #endif //MULTIPLE_HEAPS // 統計標記的對象大小 #ifdef BACKGROUND_GC total_promoted_bytes = promoted_bytes (heap_number); #endif //BACKGROUND_GC promoted_bytes (heap_number) -= promoted_bytes_live; // 統計標記階段的結束時間 #ifdef TIME_GC finish = GetCycleCount32(); mark_time = finish - start; #endif //TIME_GC dprintf(2,("---- End of mark phase ----")); }
接下來咱們看下GCHeap::Promote
函數,在plan_phase
中掃描到的對象都會調用這個函數進行標記,
這個函數名稱雖然叫Promote
可是裏面只負責對對象進行標記,被標記的對象不必定會升代
void GCHeap::Promote(Object** ppObject, ScanContext* sc, uint32_t flags) { THREAD_NUMBER_FROM_CONTEXT; #ifndef MULTIPLE_HEAPS const int thread = 0; #endif //!MULTIPLE_HEAPS uint8_t* o = (uint8_t*)*ppObject; if (o == 0) return; #ifdef DEBUG_DestroyedHandleValue // we can race with destroy handle during concurrent scan if (o == (uint8_t*)DEBUG_DestroyedHandleValue) return; #endif //DEBUG_DestroyedHandleValue HEAP_FROM_THREAD; gc_heap* hp = gc_heap::heap_of (o); dprintf (3, ("Promote %Ix", (size_t)o)); // 若是傳入的o不必定是對象的開始地址,則須要從新找到o屬於的對象 #ifdef INTERIOR_POINTERS if (flags & GC_CALL_INTERIOR) { if ((o < hp->gc_low) || (o >= hp->gc_high)) { return; } if ( (o = hp->find_object (o, hp->gc_low)) == 0) { return; } } #endif //INTERIOR_POINTERS // 啓用conservative GC時有可能會對自由對象調用這個函數,這裏須要額外判斷 #ifdef FEATURE_CONSERVATIVE_GC // For conservative GC, a value on stack may point to middle of a free object. // In this case, we don't need to promote the pointer. if (g_pConfig->GetGCConservative() && ((CObjectHeader*)o)->IsFree()) { return; } #endif // 驗證對象是否能夠標記,除錯用 #ifdef _DEBUG ((CObjectHeader*)o)->ValidatePromote(sc, flags); #else UNREFERENCED_PARAMETER(sc); #endif //_DEBUG // 若是須要標記對象固定(pinned)則調用`pin_object` // 請看上面對`PinObject`函數的描述 // `pin_object`函數會設置對象的同步索引塊 |= 0x20000000 if (flags & GC_CALL_PINNED) hp->pin_object (o, (uint8_t**) ppObject, hp->gc_low, hp->gc_high); // 若是有特殊的設置則20次固定一次對象 #ifdef STRESS_PINNING if ((++n_promote % 20) == 1) hp->pin_object (o, (uint8_t**) ppObject, hp->gc_low, hp->gc_high); #endif //STRESS_PINNING #ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING size_t promoted_size_begin = hp->promoted_bytes (thread); #endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING // 若是對象在gc範圍中則調用`mark_object_simple` // 若是對象不在gc範圍則會跳過,這也是前面提到的須要Card Table的緣由 if ((o >= hp->gc_low) && (o < hp->gc_high)) { hpt->mark_object_simple (&o THREAD_NUMBER_ARG); } // 記錄標記的大小 #ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING size_t promoted_size_end = hp->promoted_bytes (thread); if (g_fEnableARM) { if (sc->pCurrentDomain) { sc->pCurrentDomain->RecordSurvivedBytes ((promoted_size_end - promoted_size_begin), thread); } } #endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING STRESS_LOG_ROOT_PROMOTE(ppObject, o, o ? header(o)->GetMethodTable() : NULL); }
再看下mark_object_simple
函數
//this method assumes that *po is in the [low. high[ range void gc_heap::mark_object_simple (uint8_t** po THREAD_NUMBER_DCL) { uint8_t* o = *po; #ifdef MULTIPLE_HEAPS #else //MULTIPLE_HEAPS const int thread = 0; #endif //MULTIPLE_HEAPS { #ifdef SNOOP_STATS snoop_stat.objects_checked_count++; #endif //SNOOP_STATS // gc_mark1會設置對象中指向Method Table的指針 |= 1 // 若是對象是第一次標記會返回true if (gc_mark1 (o)) { // 更新gc_heap的成員slow和shigh(已標記對象的最小和最大地址) // 若是使用了mark list則把對象加到mark list中 m_boundary (o); // 記錄已標記的對象大小 size_t s = size (o); promoted_bytes (thread) += s; { // 枚舉對象o的全部成員,包括o本身 go_through_object_cl (method_table(o), o, s, poo, { uint8_t* oo = *poo; // 若是成員在gc掃描範圍中則標記該成員 if (gc_mark (oo, gc_low, gc_high)) { // 若是使用了mark list則把對象加到mark list中 m_boundary (oo); // 記錄已標記的對象大小 size_t obj_size = size (oo); promoted_bytes (thread) += obj_size; // 若是成員下還包含其餘能夠收集的成員,須要進一步標記 // 由於引用的層數可能不少致使爆棧,mark_object_simple1會使用mark_stack_array循環標記對象而不是用遞歸 if (contain_pointers_or_collectible (oo)) mark_object_simple1 (oo, oo THREAD_NUMBER_ARG); } } ); } } } }
通過標記階段之後,在堆中存活的對象都被設置了marked標記,若是對象是固定的還會被設置pinned標記
接下來是計劃階段plan_phase
:
在這個階段首先會模擬壓縮和構建Brick Table,在模擬完成後判斷是否應該進行實際的壓縮,
若是進行實際的壓縮則進入重定位階段(relocate_phase)和壓縮階段(compact_phase),不然進入清掃階段(sweep_phase),
在繼續看代碼以前咱們須要先了解計劃階段如何模擬壓縮和什麼是Brick Table。
計劃階段首先會根據相鄰的已標記的對象建立plug,用於加快處理速度和減小須要的內存空間,咱們假定一段內存中的對象以下圖
計劃階段會爲這一段對象建立2個unpinned plug
和一個pinned plug
:
第一個plug是unpinned plug
,包含了對象B, C,不固定地址
第二個plug是pinned plug
,包含了對象E, F, G,固定地址
第三個plug是unpinned plug
,包含了對象H,不固定地址
各個plug的信息保存在開始地址以前的一段內存中,結構以下
struct plug_and_gap { // 在這個plug以前有多少空間是未被標記(可回收)的 ptrdiff_t gap; // 壓縮這個plug中的對象時須要移動的偏移值,通常是負數 ptrdiff_t reloc; union { // 左邊節點和右邊節點 pair m_pair; int lr; //for clearing the entire pair in one instruction }; // 填充對象(防止覆蓋同步索引塊) plug m_plug; };
眼尖的會發現上面的圖有兩個問題
saved_post_plug
中,具體請看下面的代碼多個plug會構建成一棵樹,例如上面的三個plug會構建成這樣的樹:
第一個plug: { gap: 24, reloc: 未定義, m_pair: { left: 0, right: 0 } } 第二個plug: { gap: 132, reloc: 0, m_pair: { left: -356, right: 206 } } 第三個plug: { gap: 24, reloc: 未定義, m_pair: { left: 0, right 0 } }
第二個plug的left
和right
保存的是離子節點plug的偏移值,
第三個plug的gap
比較特殊,可能大家會以爲應該是0可是會被設置爲24(sizeof(gap_reloc_pair)),這個大小在實際複製第二個plug(compact_plug)的時候會加回來。
當計劃階段找到一個plug的開始時,
若是這個plug是pinned plug
則加到mark_stack_array
隊列中。
當計劃階段找到一個plug的結尾時,
若是這個plug是pinned plug
則設置這個plug的大小並移動隊列頂部(mark_stack_tos),
不然使用使用函數allocate_in_condemned_generations
計算把這個plug移動到前面(壓縮)時的偏移值,
allocate_in_condemned_generations
的原理請看下圖
函數allocate_in_condemned_generations
不會實際的移動內存和修改指針,它只設置了plug的reloc
成員,
這裏須要注意的是若是有pinned plug
而且前面的空間不夠,會從pinned plug
的結尾開始計算,
同時出隊列之後的plug B
在mark_stack_array
中的len
會被設置爲前面一段空間的大小,也就是32+39=71
。
如今讓咱們思考一個問題,若是咱們遇到一個對象x,如何求出對象x應該移動到的位置?
咱們須要根據對象x找到它所在的plug,而後根據這個plug的reloc
移動,查找plug使用的索引就是接下來要說的Brick Table
。
brick_table
是一個類型爲short*
的數組,用於快速索引plug,如圖
根據所屬的brick不一樣,會構建多個plug樹(避免plug樹過大),而後設置根節點的信息到brick_table
中,
brick中的值若是是正值則表示brick對應的開始地址離根節點plug的偏移值+1,
若是是負值則表示plug樹橫跨了多個brick,須要到前面的brick查找。
brick_table
相關的代碼以下,咱們能夠看到在64位下brick的大小是4096,在32位下brick的大小是2048
#if defined (_TARGET_AMD64_) #define brick_size ((size_t)4096) #else #define brick_size ((size_t)2048) #endif //_TARGET_AMD64_ inline size_t gc_heap::brick_of (uint8_t* add) { return (size_t)(add - lowest_address) / brick_size; } inline uint8_t* gc_heap::brick_address (size_t brick) { return lowest_address + (brick_size * brick); } void gc_heap::clear_brick_table (uint8_t* from, uint8_t* end) { for (size_t i = brick_of (from);i < brick_of (end); i++) brick_table[i] = 0; } //codes for the brick entries: //entry == 0 -> not assigned //entry >0 offset is entry-1 //entry <0 jump back entry bricks inline void gc_heap::set_brick (size_t index, ptrdiff_t val) { if (val < -32767) { val = -32767; } assert (val < 32767); if (val >= 0) brick_table [index] = (short)val+1; else brick_table [index] = (short)val; } inline int gc_heap::brick_entry (size_t index) { int val = brick_table [index]; if (val == 0) { return -32768; } else if (val < 0) { return val; } else return val-1; }
brick_table
中出現負值的狀況是由於plug橫跨幅度比較大,超過了單個brick的時候後面的brick就會設爲負值,
若是對象地址在上圖的1001或1002,查找這個對象對應的plug會從1000的plug樹開始。
另外1002中的值不必定須要是-2,-1也是有效的,若是是-1會一直向前查找直到找到正值的brick。
在上面咱們提到的問題能夠經過brick_table
解決,能夠看下面relocate_address
函數的代碼。
brick_table
在gc過程當中會儲存plug樹,可是在gc完成後(gc不執行時)會儲存各個brick中地址最大的plug,用於給find_first_object
等函數定位對象的開始地址使用。
pinned plug
除了會在plug樹和brick table
中,還會保存在mark_stack_array
隊列中,類型是mark
。
由於unpinned plug
和pinned plug
相鄰會致使原來的內容被plug信息覆蓋,mark
中還會保存如下的特殊信息
mark_stack_array
中的len
意義會在入隊和出隊時有所改變,
入隊時len
表明pinned plug
的大小,
出隊後len
表明pinned plug
離最後的模擬壓縮分配地址的空間(這個空間能夠變成free object)。
mark_stack_array
的結構以下圖:
入隊時mark_stack_tos
增長,出隊時mark_stack_bos
增長,空間不夠時會擴展而後mark_stack_array_length
會增長。
計劃階段模擬壓縮的時候建立plug,設置reloc
等等只是爲了接下來的壓縮作準備,既不會修改指針地址也不會移動內存。
在作完這些工做以後計劃階段會首先判斷應不該該進行壓縮,若是不進行壓縮而是進行清掃,這些計算結果都會浪費掉。
判斷是否使用壓縮的根據主要有
其餘還有一些零碎的判斷,將在下面的decide_on_compacting
函數的代碼中講解。
在不少介紹.Net GC的書籍中都有提到過,通過GC之後對象會升代,例如gen 0中的對象在一次GC後若是存活下來會變爲gen 1。
在CoreCLR中,對象的升代須要知足必定條件,某些特殊狀況下不會升代,甚至會降代(gen1變爲gen0)。
對象升代的條件以下:
dt_low_card_table_efficiency_p
成立時會啓用升代
dt_low_card_table_efficiency_p
查看該處的解釋promoted_bytes (i) > m
查看該處的解釋若是升代的條件不知足,則原來在gen 0的對象GC後仍然會在gen 0,
某些特殊條件下還會發生降代,以下圖:
在模擬壓縮時,原來在gen 1的對象會歸到gen 2(pinned object不必定),原來在gen 0的對象會歸到gen 1,
可是若是全部unpinned plug都已經壓縮到前面,後面還有殘留的pinned plug時,後面殘留的pinned plug中的對象則會不升代或者降代,
當這種狀況發生時計劃階段會設置demotion_low
來標記被降代的範圍。
若是最終選擇了清掃(sweep)則上圖中的狀況不會發生。
計劃階段在模擬壓縮的時候還會計劃代邊界(generation::plan_allocation_start),
計劃代邊界的工做主要在process_ephemeral_boundaries
, plan_generation_start
, plan_generation_starts
函數中完成。
大部分狀況下函數process_ephemeral_boundaries
會用來計劃gen 1的邊界,若是不升代這個函數還會計劃gen 0的邊界,
當判斷當前計劃的plug大於或等於下一代的邊界時,例如大於等於gen 0的邊界時則會設置gen 1的邊界在這個plug的前面。
最終選擇壓縮(compact)時,會把新的代邊界設置成計劃代邊界(請看fix_generation_bounds
函數),
最終選擇清掃(sweep)時,計劃代邊界不會被使用(請看make_free_lists
函數和make_free_list_in_brick
函數)。
gc_heap::plan_phase
函數的代碼以下
void gc_heap::plan_phase (int condemned_gen_number) { // 若是收集代是gen 1則記錄原來gen 2的大小 size_t old_gen2_allocated = 0; size_t old_gen2_size = 0; if (condemned_gen_number == (max_generation - 1)) { old_gen2_allocated = generation_free_list_allocated (generation_of (max_generation)); old_gen2_size = generation_size (max_generation); } assert (settings.concurrent == FALSE); // 統計計劃階段的開始時間 // %type% category = quote (plan); #ifdef TIME_GC unsigned start; unsigned finish; start = GetCycleCount32(); #endif //TIME_GC dprintf (2,("---- Plan Phase ---- Condemned generation %d, promotion: %d", condemned_gen_number, settings.promotion ? 1 : 0)); // 收集代的對象 generation* condemned_gen1 = generation_of (condemned_gen_number); // 判斷以前是否使用了mark list // 標記對象較少時用mark list能夠提高速度 #ifdef MARK_LIST BOOL use_mark_list = FALSE; uint8_t** mark_list_next = &mark_list[0]; #ifdef GC_CONFIG_DRIVEN dprintf (3, ("total number of marked objects: %Id (%Id)", (mark_list_index - &mark_list[0]), ((mark_list_end - &mark_list[0])))); #else dprintf (3, ("mark_list length: %Id", (mark_list_index - &mark_list[0]))); #endif //GC_CONFIG_DRIVEN if ((condemned_gen_number < max_generation) && (mark_list_index <= mark_list_end) #ifdef BACKGROUND_GC && (!recursive_gc_sync::background_running_p()) #endif //BACKGROUND_GC ) { #ifndef MULTIPLE_HEAPS _sort (&mark_list[0], mark_list_index-1, 0); //printf ("using mark list at GC #%d", dd_collection_count (dynamic_data_of (0))); //verify_qsort_array (&mark_list[0], mark_list_index-1); #endif //!MULTIPLE_HEAPS use_mark_list = TRUE; get_gc_data_per_heap()->set_mechanism_bit (gc_mark_list_bit); } else { dprintf (3, ("mark_list not used")); } #endif //MARK_LIST // 清除read only segment中的marked bit #ifdef FEATURE_BASICFREEZE if ((generation_start_segment (condemned_gen1) != ephemeral_heap_segment) && ro_segments_in_range) { sweep_ro_segments (generation_start_segment (condemned_gen1)); } #endif // FEATURE_BASICFREEZE // 根據以前使用m_boundary記錄的slow和shigh快速清掃slow前面和shigh後面的垃圾對象 // shigh等於0表示無對象存活 // if (shigh != (uint8_t*)0) // 對於slow, 調用make_unused_array // 對於shigh, 設置heap_segment_allocated // 對於範圍外的segment, heap_segment_allocated (seg) = heap_segment_mem (seg); // 整個segment都被清空,後面可刪除 // else // 第一個segment, heap_segment_allocated (seg) = generation_allocation_start (condemned_gen1); // 後面的segment, heap_segment_allocated (seg) = heap_segment_mem (seg); // 整個segment都被清空,後面可刪除 #ifndef MULTIPLE_HEAPS if (shigh != (uint8_t*)0) { heap_segment* seg = heap_segment_rw (generation_start_segment (condemned_gen1)); PREFIX_ASSUME(seg != NULL); heap_segment* fseg = seg; do { if (slow > heap_segment_mem (seg) && slow < heap_segment_reserved (seg)) { if (seg == fseg) { uint8_t* o = generation_allocation_start (condemned_gen1) + Align (size (generation_allocation_start (condemned_gen1))); if (slow > o) { assert ((slow - o) >= (int)Align (min_obj_size)); #ifdef BACKGROUND_GC if (current_c_gc_state == c_gc_state_marking) { bgc_clear_batch_mark_array_bits (o, slow); } #endif //BACKGROUND_GC make_unused_array (o, slow - o); } } else { assert (condemned_gen_number == max_generation); make_unused_array (heap_segment_mem (seg), slow - heap_segment_mem (seg)); } } if (in_range_for_segment (shigh, seg)) { #ifdef BACKGROUND_GC if (current_c_gc_state == c_gc_state_marking) { bgc_clear_batch_mark_array_bits ((shigh + Align (size (shigh))), heap_segment_allocated (seg)); } #endif //BACKGROUND_GC heap_segment_allocated (seg) = shigh + Align (size (shigh)); } // test if the segment is in the range of [slow, shigh] if (!((heap_segment_reserved (seg) >= slow) && (heap_segment_mem (seg) <= shigh))) { // shorten it to minimum heap_segment_allocated (seg) = heap_segment_mem (seg); } seg = heap_segment_next_rw (seg); } while (seg); } else { heap_segment* seg = heap_segment_rw (generation_start_segment (condemned_gen1)); PREFIX_ASSUME(seg != NULL); heap_segment* sseg = seg; do { // shorten it to minimum if (seg == sseg) { // no survivors make all generations look empty uint8_t* o = generation_allocation_start (condemned_gen1) + Align (size (generation_allocation_start (condemned_gen1))); #ifdef BACKGROUND_GC if (current_c_gc_state == c_gc_state_marking) { bgc_clear_batch_mark_array_bits (o, heap_segment_allocated (seg)); } #endif //BACKGROUND_GC heap_segment_allocated (seg) = o; } else { assert (condemned_gen_number == max_generation); #ifdef BACKGROUND_GC if (current_c_gc_state == c_gc_state_marking) { bgc_clear_batch_mark_array_bits (heap_segment_mem (seg), heap_segment_allocated (seg)); } #endif //BACKGROUND_GC heap_segment_allocated (seg) = heap_segment_mem (seg); } seg = heap_segment_next_rw (seg); } while (seg); } #endif //MULTIPLE_HEAPS // 當前計劃的segment,會隨着計劃向後移動 heap_segment* seg1 = heap_segment_rw (generation_start_segment (condemned_gen1)); PREFIX_ASSUME(seg1 != NULL); // 當前計劃的segment的結束地址 uint8_t* end = heap_segment_allocated (seg1); // 收集代的第一個對象(地址) uint8_t* first_condemned_address = generation_allocation_start (condemned_gen1); // 當前計劃的對象 uint8_t* x = first_condemned_address; assert (!marked (x)); // 當前plug的結束地址 uint8_t* plug_end = x; // 當前plug樹的根節點 uint8_t* tree = 0; // 構建plug樹使用的序列 size_t sequence_number = 0; // 上一次的plug節點 uint8_t* last_node = 0; // 當前計劃的brick size_t current_brick = brick_of (x); // 是否從計劃代開始模擬分配(這個變量後面還會設爲true) BOOL allocate_in_condemned = ((condemned_gen_number == max_generation)|| (settings.promotion == FALSE)); // 當前計劃的舊代和新代,這兩個變量用於從新決定代邊界(generation_allocation_start) int active_old_gen_number = condemned_gen_number; int active_new_gen_number = (allocate_in_condemned ? condemned_gen_number: (1 + condemned_gen_number)); // 收集代的上一代(若是收集代是gen 2這裏會設爲gen 2) generation* older_gen = 0; // 模擬分配的代 generation* consing_gen = condemned_gen1; // older_gen的原始數據備份 alloc_list r_free_list [MAX_BUCKET_COUNT]; size_t r_free_list_space = 0; size_t r_free_obj_space = 0; size_t r_older_gen_free_list_allocated = 0; size_t r_older_gen_condemned_allocated = 0; size_t r_older_gen_end_seg_allocated = 0; uint8_t* r_allocation_pointer = 0; uint8_t* r_allocation_limit = 0; uint8_t* r_allocation_start_region = 0; heap_segment* r_allocation_segment = 0; #ifdef FREE_USAGE_STATS size_t r_older_gen_free_space[NUM_GEN_POWER2]; #endif //FREE_USAGE_STATS // 在計劃以前備份older_gen的數據 if ((condemned_gen_number < max_generation)) { older_gen = generation_of (min (max_generation, 1 + condemned_gen_number)); generation_allocator (older_gen)->copy_to_alloc_list (r_free_list); r_free_list_space = generation_free_list_space (older_gen); r_free_obj_space = generation_free_obj_space (older_gen); #ifdef FREE_USAGE_STATS memcpy (r_older_gen_free_space, older_gen->gen_free_spaces, sizeof (r_older_gen_free_space)); #endif //FREE_USAGE_STATS generation_allocate_end_seg_p (older_gen) = FALSE; r_older_gen_free_list_allocated = generation_free_list_allocated (older_gen); r_older_gen_condemned_allocated = generation_condemned_allocated (older_gen); r_older_gen_end_seg_allocated = generation_end_seg_allocated (older_gen); r_allocation_limit = generation_allocation_limit (older_gen); r_allocation_pointer = generation_allocation_pointer (older_gen); r_allocation_start_region = generation_allocation_context_start_region (older_gen); r_allocation_segment = generation_allocation_segment (older_gen); heap_segment* start_seg = heap_segment_rw (generation_start_segment (older_gen)); PREFIX_ASSUME(start_seg != NULL); if (start_seg != ephemeral_heap_segment) { assert (condemned_gen_number == (max_generation - 1)); while (start_seg && (start_seg != ephemeral_heap_segment)) { assert (heap_segment_allocated (start_seg) >= heap_segment_mem (start_seg)); assert (heap_segment_allocated (start_seg) <= heap_segment_reserved (start_seg)); heap_segment_plan_allocated (start_seg) = heap_segment_allocated (start_seg); start_seg = heap_segment_next_rw (start_seg); } } } // 重設收集代之後的的全部segment的plan_allocated(計劃分配的對象大小合計) //reset all of the segment allocated sizes { heap_segment* seg2 = heap_segment_rw (generation_start_segment (condemned_gen1)); PREFIX_ASSUME(seg2 != NULL); while (seg2) { heap_segment_plan_allocated (seg2) = heap_segment_mem (seg2); seg2 = heap_segment_next_rw (seg2); } } // 重設gen 0 ~ 收集代的數據 int condemned_gn = condemned_gen_number; int bottom_gen = 0; init_free_and_plug(); while (condemned_gn >= bottom_gen) { generation* condemned_gen2 = generation_of (condemned_gn); generation_allocator (condemned_gen2)->clear(); generation_free_list_space (condemned_gen2) = 0; generation_free_obj_space (condemned_gen2) = 0; generation_allocation_size (condemned_gen2) = 0; generation_condemned_allocated (condemned_gen2) = 0; generation_pinned_allocated (condemned_gen2) = 0; generation_free_list_allocated(condemned_gen2) = 0; generation_end_seg_allocated (condemned_gen2) = 0; // 執行清掃(sweep)時對應代增長的固定對象(pinned object)大小 generation_pinned_allocation_sweep_size (condemned_gen2) = 0; // 執行壓縮(compact)時對應代增長的固定對象(pinned object)大小 generation_pinned_allocation_compact_size (condemned_gen2) = 0; #ifdef FREE_USAGE_STATS generation_pinned_free_obj_space (condemned_gen2) = 0; generation_allocated_in_pinned_free (condemned_gen2) = 0; generation_allocated_since_last_pin (condemned_gen2) = 0; #endif //FREE_USAGE_STATS // 計劃的代邊界 generation_plan_allocation_start (condemned_gen2) = 0; generation_allocation_segment (condemned_gen2) = heap_segment_rw (generation_start_segment (condemned_gen2)); PREFIX_ASSUME(generation_allocation_segment(condemned_gen2) != NULL); // 設置分配上下文地址,模擬壓縮時使用 if (generation_start_segment (condemned_gen2) != ephemeral_heap_segment) { generation_allocation_pointer (condemned_gen2) = heap_segment_mem (generation_allocation_segment (condemned_gen2)); } else { generation_allocation_pointer (condemned_gen2) = generation_allocation_start (condemned_gen2); } generation_allocation_limit (condemned_gen2) = generation_allocation_pointer (condemned_gen2); generation_allocation_context_start_region (condemned_gen2) = generation_allocation_pointer (condemned_gen2); condemned_gn--; } // 在處理全部對象以前是否要先決定一個代的邊界 // 不升代或者收集代是gen 2(Full GC)時須要 BOOL allocate_first_generation_start = FALSE; if (allocate_in_condemned) { allocate_first_generation_start = TRUE; } dprintf(3,( " From %Ix to %Ix", (size_t)x, (size_t)end)); // 記錄對象降代(原來gen 1的對象變爲gen 0)的狀況 // 關於不升代和降代的條件和處理將在下面解釋 demotion_low = MAX_PTR; demotion_high = heap_segment_allocated (ephemeral_heap_segment); // 判斷是否應該阻止gen 1中的固定對象降代 // 若是隻是收集緣由只是由於dt_low_card_table_efficiency_p則須要阻止降代 // demote_gen1_p = false時會在下面調用advance_pins_for_demotion函數 // If we are doing a gen1 only because of cards, it means we should not demote any pinned plugs // from gen1. They should get promoted to gen2. demote_gen1_p = !(settings.promotion && (settings.condemned_generation == (max_generation - 1)) && gen_to_condemn_reasons.is_only_condition (gen_low_card_p)); total_ephemeral_size = 0; // 打印除錯信息 print_free_and_plug ("BP"); // 打印除錯信息 for (int gen_idx = 0; gen_idx <= max_generation; gen_idx++) { generation* temp_gen = generation_of (gen_idx); dprintf (2, ("gen%d start %Ix, plan start %Ix", gen_idx, generation_allocation_start (temp_gen), generation_plan_allocation_start (temp_gen))); } // 觸發etw時間 BOOL fire_pinned_plug_events_p = ETW_EVENT_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_Context, PinPlugAtGCTime); size_t last_plug_len = 0; // 開始模擬壓縮 // 會建立plug,設置brick table和模擬plug的移動 while (1) { // 應該處理下個segment if (x >= end) { assert (x == end); assert (heap_segment_allocated (seg1) == end); heap_segment_allocated (seg1) = plug_end; // 設置brick table current_brick = update_brick_table (tree, current_brick, x, plug_end); dprintf (3, ("end of seg: new tree, sequence# 0")); sequence_number = 0; tree = 0; // 有下一個segment,繼續處理 if (heap_segment_next_rw (seg1)) { seg1 = heap_segment_next_rw (seg1); end = heap_segment_allocated (seg1); plug_end = x = heap_segment_mem (seg1); current_brick = brick_of (x); dprintf(3,( " From %Ix to %Ix", (size_t)x, (size_t)end)); continue; } // 無下一個segment,跳出模擬壓縮的循環 else { break; } } // 上一個plug是否unpinned plug BOOL last_npinned_plug_p = FALSE; // 上一個plug是否pinned plug BOOL last_pinned_plug_p = FALSE; // 上一個pinned plug的地址,合併pinned plug時使用 // last_pinned_plug is the beginning of the last pinned plug. If we merge a plug into a pinned // plug we do not change the value of last_pinned_plug. This happens with artificially pinned plugs - // it can be merged with a previous pinned plug and a pinned plug after it can be merged with it. uint8_t* last_pinned_plug = 0; size_t num_pinned_plugs_in_plug = 0; // 當前plug的最後一個對象的地址 uint8_t* last_object_in_plug = 0; // 枚舉segment中的對象,若是第一個對象未被標記不會進入如下的處理 while ((x < end) && marked (x)) { // 記錄plug的開始 uint8_t* plug_start = x; uint8_t* saved_plug_end = plug_end; // 當前plug中的對象是否pinned object // 會輪流切換 BOOL pinned_plug_p = FALSE; BOOL npin_before_pin_p = FALSE; BOOL saved_last_npinned_plug_p = last_npinned_plug_p; uint8_t* saved_last_object_in_plug = last_object_in_plug; BOOL merge_with_last_pin_p = FALSE; size_t added_pinning_size = 0; size_t artificial_pinned_size = 0; // 預先保存一部分plug信息 // 設置這個plug和上一個plug的結尾之間的gap // 若是當前plug是pinned plug // - 調用enque_pinned_plug把plug信息保存到mark_stack_array隊列 // - enque_pinned_plug不會設置長度(len)和移動隊列頂部(mark_stack_tos),這部分工做會在set_pinned_info完成 // - 檢測當前pinned plug是否覆蓋了前一個unpinned plug的結尾 // - 若是覆蓋了須要把原來的內容複製到saved_pre_plug和saved_pre_plug_reloc (函數enque_pinned_plug) // 若是當前plug是unpinned plug // - 檢測當前unpinned plug是否覆蓋了前一個pinned plug的結尾 // - 若是覆蓋了須要把原來的內容複製到saved_post_plug和saved_post_plug_reloc (函數save_post_plug_info) store_plug_gap_info (plug_start, plug_end, last_npinned_plug_p, last_pinned_plug_p, last_pinned_plug, pinned_plug_p, last_object_in_plug, merge_with_last_pin_p, last_plug_len); #ifdef FEATURE_STRUCTALIGN int requiredAlignment = ((CObjectHeader*)plug_start)->GetRequiredAlignment(); size_t alignmentOffset = OBJECT_ALIGNMENT_OFFSET; #endif // FEATURE_STRUCTALIGN { // 枚舉接下來的對象,若是對象未被標記,或者對象是否固定和pinned_plug_p不一致則中斷 // 這裏枚舉到的對象都會歸到同一個plug裏面 uint8_t* xl = x; while ((xl < end) && marked (xl) && (pinned (xl) == pinned_plug_p)) { assert (xl < end); // 清除pinned bit // 像前面所說的,GC裏面marked和pinned標記都是臨時使用的,在計劃階段會被清除 if (pinned(xl)) { clear_pinned (xl); } #ifdef FEATURE_STRUCTALIGN else { int obj_requiredAlignment = ((CObjectHeader*)xl)->GetRequiredAlignment(); if (obj_requiredAlignment > requiredAlignment) { requiredAlignment = obj_requiredAlignment; alignmentOffset = xl - plug_start + OBJECT_ALIGNMENT_OFFSET; } } #endif // FEATURE_STRUCTALIGN // 清除marked bit clear_marked (xl); dprintf(4, ("+%Ix+", (size_t)xl)); assert ((size (xl) > 0)); assert ((size (xl) <= LARGE_OBJECT_SIZE)); // 記錄當前plug的最後一個對象 last_object_in_plug = xl; // 下一個對象 xl = xl + Align (size (xl)); Prefetch (xl); } BOOL next_object_marked_p = ((xl < end) && marked (xl)); // 若是當前plug是pinned plug但下一個不是,表明當前plug的結尾須要被覆蓋掉作下一個plug的信息 // 咱們不想動pinned plug的內容,因此這裏須要犧牲下一個對象,把下一個對象拉到這個plug裏面 if (pinned_plug_p) { // If it is pinned we need to extend to the next marked object as we can't use part of // a pinned object to make the artificial gap (unless the last 3 ptr sized words are all // references but for now I am just using the next non pinned object for that). if (next_object_marked_p) { clear_marked (xl); last_object_in_plug = xl; size_t extra_size = Align (size (xl)); xl = xl + extra_size; added_pinning_size = extra_size; } } else { // 當前plug是unpinned plug,下一個plug是pinned plug if (next_object_marked_p) npin_before_pin_p = TRUE; } assert (xl <= end); x = xl; } dprintf (3, ( "%Ix[", (size_t)x)); // 設置plug的結尾 plug_end = x; // plug大小 = 結尾 - 開頭 size_t ps = plug_end - plug_start; last_plug_len = ps; dprintf (3, ( "%Ix[(%Ix)", (size_t)x, ps)); uint8_t* new_address = 0; // 有時候若是一個unpinned plug很大,咱們想人工固定它(artificially pinned plug) // 若是前一個plug也是pinned plug則和前一個plug整合到一個,不然進入mark_stack_array隊列中 if (!pinned_plug_p) { if (allocate_in_condemned && (settings.condemned_generation == max_generation) && (ps > (OS_PAGE_SIZE))) { ptrdiff_t reloc = plug_start - generation_allocation_pointer (consing_gen); //reloc should >=0 except when we relocate //across segments and the dest seg is higher then the src if ((ps > (8*OS_PAGE_SIZE)) && (reloc > 0) && ((size_t)reloc < (ps/16))) { dprintf (3, ("Pinning %Ix; reloc would have been: %Ix", (size_t)plug_start, reloc)); // The last plug couldn't have been a npinned plug or it would have // included this plug. assert (!saved_last_npinned_plug_p); if (last_pinned_plug) { dprintf (3, ("artificially pinned plug merged with last pinned plug")); merge_with_last_pin_p = TRUE; } else { enque_pinned_plug (plug_start, FALSE, 0); last_pinned_plug = plug_start; } convert_to_pinned_plug (last_npinned_plug_p, last_pinned_plug_p, pinned_plug_p, ps, artificial_pinned_size); } } } // 若是在作Full GC或者不升代,決定第一個代的邊界 // plan_generation_start用於計劃代的邊界(generation_plan_generation_start) // Full GC時gen 2的邊界會在這裏決定 if (allocate_first_generation_start) { allocate_first_generation_start = FALSE; plan_generation_start (condemned_gen1, consing_gen, plug_start); assert (generation_plan_allocation_start (condemned_gen1)); } // 若是模擬的segment是ephemeral heap segment // 在這裏決定gen 1的邊界 // 若是不升代這裏也會決定gen 0的邊界 if (seg1 == ephemeral_heap_segment) { process_ephemeral_boundaries (plug_start, active_new_gen_number, active_old_gen_number, consing_gen, allocate_in_condemned); } dprintf (3, ("adding %Id to gen%d surv", ps, active_old_gen_number)); // 統計存活的對象大小 dynamic_data* dd_active_old = dynamic_data_of (active_old_gen_number); dd_survived_size (dd_active_old) += ps; // 模擬壓縮的時候有可能會要求把當前unpinned plug轉換爲pinned plug BOOL convert_to_pinned_p = FALSE; // 若是plug是unpinned plug,模擬壓縮 if (!pinned_plug_p) { #if defined (RESPECT_LARGE_ALIGNMENT) || defined (FEATURE_STRUCTALIGN) dd_num_npinned_plugs (dd_active_old)++; #endif //RESPECT_LARGE_ALIGNMENT || FEATURE_STRUCTALIGN // 更新統計信息 add_gen_plug (active_old_gen_number, ps); if (allocate_in_condemned) { verify_pins_with_post_plug_info("before aic"); // 在收集代分配,必要時跳過pinned plug,返回新的地址 new_address = allocate_in_condemned_generations (consing_gen, ps, active_old_gen_number, #ifdef SHORT_PLUGS &convert_to_pinned_p, (npin_before_pin_p ? plug_end : 0), seg1, #endif //SHORT_PLUGS plug_start REQD_ALIGN_AND_OFFSET_ARG); verify_pins_with_post_plug_info("after aic"); } else { // 在上一代分配,必要時跳過pinned plug,返回新的地址 new_address = allocate_in_older_generation (older_gen, ps, active_old_gen_number, plug_start REQD_ALIGN_AND_OFFSET_ARG); if (new_address != 0) { if (settings.condemned_generation == (max_generation - 1)) { dprintf (3, (" NA: %Ix-%Ix -> %Ix, %Ix (%Ix)", plug_start, plug_end, (size_t)new_address, (size_t)new_address + (plug_end - plug_start), (size_t)(plug_end - plug_start))); } } else { // 失敗時(空間不足)改成在收集代分配 allocate_in_condemned = TRUE; new_address = allocate_in_condemned_generations (consing_gen, ps, active_old_gen_number, #ifdef SHORT_PLUGS &convert_to_pinned_p, (npin_before_pin_p ? plug_end : 0), seg1, #endif //SHORT_PLUGS plug_start REQD_ALIGN_AND_OFFSET_ARG); } } // 若是要求把當前unpinned plug轉換爲pinned plug if (convert_to_pinned_p) { assert (last_npinned_plug_p != FALSE); assert (last_pinned_plug_p == FALSE); convert_to_pinned_plug (last_npinned_plug_p, last_pinned_plug_p, pinned_plug_p, ps, artificial_pinned_size); enque_pinned_plug (plug_start, FALSE, 0); last_pinned_plug = plug_start; } else { // 找不到空間(不移動這個plug)時驗證是在ephemeral heap segment的末尾 // 這裏還不會設置reloc,到下面的set_node_relocation_distance纔會設 if (!new_address) { //verify that we are at then end of the ephemeral segment assert (generation_allocation_segment (consing_gen) == ephemeral_heap_segment); //verify that we are near the end assert ((generation_allocation_pointer (consing_gen) + Align (ps)) < heap_segment_allocated (ephemeral_heap_segment)); assert ((generation_allocation_pointer (consing_gen) + Align (ps)) > (heap_segment_allocated (ephemeral_heap_segment) + Align (min_obj_size))); } else { #ifdef SIMPLE_DPRINTF dprintf (3, ("(%Ix)[%Ix->%Ix, NA: [%Ix(%Id), %Ix[: %Ix(%d)", (size_t)(node_gap_size (plug_start)), plug_start, plug_end, (size_t)new_address, (size_t)(plug_start - new_address), (size_t)new_address + ps, ps, (is_plug_padded (plug_start) ? 1 : 0))); #endif //SIMPLE_DPRINTF #ifdef SHORT_PLUGS if (is_plug_padded (plug_start)) { dprintf (3, ("%Ix was padded", plug_start)); dd_padding_size (dd_active_old) += Align (min_obj_size); } #endif //SHORT_PLUGS } } } // 若是當前plug是pinned plug if (pinned_plug_p) { if (fire_pinned_plug_events_p) FireEtwPinPlugAtGCTime(plug_start, plug_end, (merge_with_last_pin_p ? 0 : (uint8_t*)node_gap_size (plug_start)), GetClrInstanceId()); // 和上一個pinned plug合併 if (merge_with_last_pin_p) { merge_with_last_pinned_plug (last_pinned_plug, ps); } // 設置隊列中的pinned plug大小(len)並移動隊列頂部(mark_stack_tos++) else { assert (last_pinned_plug == plug_start); set_pinned_info (plug_start, ps, consing_gen); } // pinned plug不能移動,新地址和原地址同樣 new_address = plug_start; dprintf (3, ( "(%Ix)PP: [%Ix, %Ix[%Ix](m:%d)", (size_t)(node_gap_size (plug_start)), (size_t)plug_start, (size_t)plug_end, ps, (merge_with_last_pin_p ? 1 : 0))); // 統計存活對象的大小,固定對象的大小和人工固定對象的大小 dprintf (3, ("adding %Id to gen%d pinned surv", plug_end - plug_start, active_old_gen_number)); dd_pinned_survived_size (dd_active_old) += plug_end - plug_start; dd_added_pinned_size (dd_active_old) += added_pinning_size; dd_artificial_pinned_survived_size (dd_active_old) += artificial_pinned_size; // 若是須要禁止降代gen 1的對象,記錄在gen 1中最後一個pinned plug的結尾 if (!demote_gen1_p && (active_old_gen_number == (max_generation - 1))) { last_gen1_pin_end = plug_end; } } #ifdef _DEBUG // detect forward allocation in the same segment assert (!((new_address > plug_start) && (new_address < heap_segment_reserved (seg1)))); #endif //_DEBUG // 若是不合併到上一個pinned plug // 在這裏能夠設置偏移值(reloc)和更新brick table了 if (!merge_with_last_pin_p) { // 若是已經在下一個brick // 把以前的plug樹設置到以前的brick中,並重設plug樹 // 若是以前的plug跨了多個brick,update_brick_table會設置後面的brick爲-1 if (current_brick != brick_of (plug_start)) { current_brick = update_brick_table (tree, current_brick, plug_start, saved_plug_end); sequence_number = 0; tree = 0; } // 更新plug的偏移值(reloc) // 這裏的偏移值會用在後面的重定位階段(relocate_phase)和壓縮階段(compact_phase) set_node_relocation_distance (plug_start, (new_address - plug_start)); // 構建plug樹 if (last_node && (node_relocation_distance (last_node) == (node_relocation_distance (plug_start) + (int)node_gap_size (plug_start)))) { //dprintf(3,( " Lb")); dprintf (3, ("%Ix Lb", plug_start)); set_node_left (plug_start); } if (0 == sequence_number) { dprintf (2, ("sn: 0, tree is set to %Ix", plug_start)); tree = plug_start; } verify_pins_with_post_plug_info("before insert node"); tree = insert_node (plug_start, ++sequence_number, tree, last_node); dprintf (3, ("tree is %Ix (b: %Ix) after insert_node", tree, brick_of (tree))); last_node = plug_start; // 這個處理只用於除錯 // 若是這個plug是unpinned plug而且覆蓋了上一個pinned plug的結尾 // 把覆蓋的內容複製到pinned plug關聯的saved_post_plug_debug #ifdef _DEBUG // If we detect if the last plug is pinned plug right before us, we should save this gap info if (!pinned_plug_p) { if (mark_stack_tos > 0) { mark& m = mark_stack_array[mark_stack_tos - 1]; if (m.has_post_plug_info()) { uint8_t* post_plug_info_start = m.saved_post_plug_info_start; size_t* current_plug_gap_start = (size_t*)(plug_start - sizeof (plug_and_gap)); if ((uint8_t*)current_plug_gap_start == post_plug_info_start) { dprintf (3, ("Ginfo: %Ix, %Ix, %Ix", *current_plug_gap_start, *(current_plug_gap_start + 1), *(current_plug_gap_start + 2))); memcpy (&(m.saved_post_plug_debug), current_plug_gap_start, sizeof (gap_reloc_pair)); } } } } #endif //_DEBUG verify_pins_with_post_plug_info("after insert node"); } } if (num_pinned_plugs_in_plug > 1) { dprintf (3, ("more than %Id pinned plugs in this plug", num_pinned_plugs_in_plug)); } // 跳過未標記的對象找到下一個已標記的對象 // 若是有mark_list能夠加快找到下一個已標記對象的速度 { #ifdef MARK_LIST if (use_mark_list) { while ((mark_list_next < mark_list_index) && (*mark_list_next <= x)) { mark_list_next++; } if ((mark_list_next < mark_list_index) #ifdef MULTIPLE_HEAPS && (*mark_list_next < end) //for multiple segments #endif //MULTIPLE_HEAPS ) x = *mark_list_next; else x = end; } else #endif //MARK_LIST { uint8_t* xl = x; #ifdef BACKGROUND_GC if (current_c_gc_state == c_gc_state_marking) { assert (recursive_gc_sync::background_running_p()); while ((xl < end) && !marked (xl)) { dprintf (4, ("-%Ix-", (size_t)xl)); assert ((size (xl) > 0)); background_object_marked (xl, TRUE); xl = xl + Align (size (xl)); Prefetch (xl); } } else #endif //BACKGROUND_GC { // 跳過未標記的對象 while ((xl < end) && !marked (xl)) { dprintf (4, ("-%Ix-", (size_t)xl)); assert ((size (xl) > 0)); xl = xl + Align (size (xl)); Prefetch (xl); } } assert (xl <= end); // 找到了下一個已標記的對象,或者當前segment中的對象已經搜索完畢 x = xl; } } } // 處理mark_stack_array中還沒有出隊的pinned plug // 這些plug已經在全部已壓縮的unpinned plug後面,咱們能夠把這些pinned plug降級(降到gen 0),也能夠防止它們降級 while (!pinned_plug_que_empty_p()) { // 計算代邊界和處理降代 // 不在ephemeral heap segment的pinned plug不會被降代 // 前面調用的process_ephemeral_boundaries中有相同的處理 if (settings.promotion) { uint8_t* pplug = pinned_plug (oldest_pin()); if (in_range_for_segment (pplug, ephemeral_heap_segment)) { consing_gen = ensure_ephemeral_heap_segment (consing_gen); //allocate all of the generation gaps while (active_new_gen_number > 0) { active_new_gen_number--; if (active_new_gen_number == (max_generation - 1)) { // 若是要防止gen 1的pinned plug降代則須要調用調用advance_pins_for_demotion跳過(出隊)它們 // 在原來gen 0中的pinned plug不會改變 maxgen_pinned_compact_before_advance = generation_pinned_allocation_compact_size (generation_of (max_generation)); if (!demote_gen1_p) advance_pins_for_demotion (consing_gen); } // 計劃剩餘的代邊界 generation* gen = generation_of (active_new_gen_number); plan_generation_start (gen, consing_gen, 0); // 代邊界被設置到pinned plug以前的時候須要記錄降代的範圍(降代已經實際發生,設置demotion_low是記錄降代的範圍) if (demotion_low == MAX_PTR) { demotion_low = pplug; dprintf (3, ("end plan: dlow->%Ix", demotion_low)); } dprintf (2, ("(%d)gen%d plan start: %Ix", heap_number, active_new_gen_number, (size_t)generation_plan_allocation_start (gen))); assert (generation_plan_allocation_start (gen)); } } } // 全部pinned plug都已出隊時跳出 if (pinned_plug_que_empty_p()) break; // 出隊一個pinned plug size_t entry = deque_pinned_plug(); mark* m = pinned_plug_of (entry); uint8_t* plug = pinned_plug (m); size_t len = pinned_len (m); // 檢測這個pinned plug是否在cosing_gen的allocation segment以外 // 若是不在須要調整allocation segment,等會須要把generation_allocation_pointer設置爲plug + len // detect pinned block in different segment (later) than // allocation segment heap_segment* nseg = heap_segment_rw (generation_allocation_segment (consing_gen)); while ((plug < generation_allocation_pointer (consing_gen)) || (plug >= heap_segment_allocated (nseg))) { assert ((plug < heap_segment_mem (nseg)) || (plug > heap_segment_reserved (nseg))); //adjust the end of the segment to be the end of the plug assert (generation_allocation_pointer (consing_gen)>= heap_segment_mem (nseg)); assert (generation_allocation_pointer (consing_gen)<= heap_segment_committed (nseg)); heap_segment_plan_allocated (nseg) = generation_allocation_pointer (consing_gen); //switch allocation segment nseg = heap_segment_next_rw (nseg); generation_allocation_segment (consing_gen) = nseg; //reset the allocation pointer and limits generation_allocation_pointer (consing_gen) = heap_segment_mem (nseg); } // 出隊之後設置len = pinned plug - generation_allocation_pointer (consing_gen) // 表示pinned plug的開始地址離最後的模擬壓縮分配地址的空間,這個空間能夠變成free object set_new_pin_info (m, generation_allocation_pointer (consing_gen)); dprintf (2, ("pin %Ix b: %Ix->%Ix", plug, brick_of (plug), (size_t)(brick_table[brick_of (plug)]))); // 設置模擬壓縮分配地址到plug的結尾 generation_allocation_pointer (consing_gen) = plug + len; generation_allocation_limit (consing_gen) = generation_allocation_pointer (consing_gen); //Add the size of the pinned plug to the right pinned allocations //find out which gen this pinned plug came from int frgn = object_gennum (plug); // 統計清掃時會多出的pinned object大小 // 加到上一代中(pinned object升代) if ((frgn != (int)max_generation) && settings.promotion) { generation_pinned_allocation_sweep_size ((generation_of (frgn +1))) += len; } } // 計劃剩餘全部代的邊界 // 大部分狀況下(升代 + 無降代)這裏會設置gen 0的邊界,也就是在現有的全部存活對象以後 plan_generation_starts (consing_gen); // 打印除錯信息 print_free_and_plug ("AP"); // 打印除錯信息 { #ifdef SIMPLE_DPRINTF for (int gen_idx = 0; gen_idx <= max_generation; gen_idx++) { generation* temp_gen = generation_of (gen_idx); dynamic_data* temp_dd = dynamic_data_of (gen_idx); int added_pinning_ratio = 0; int artificial_pinned_ratio = 0; if (dd_pinned_survived_size (temp_dd) != 0) { added_pinning_ratio = (int)((float)dd_added_pinned_size (temp_dd) * 100 / (float)dd_pinned_survived_size (temp_dd)); artificial_pinned_ratio = (int)((float)dd_artificial_pinned_survived_size (temp_dd) * 100 / (float)dd_pinned_survived_size (temp_dd)); } size_t padding_size = #ifdef SHORT_PLUGS dd_padding_size (temp_dd); #else 0; #endif //SHORT_PLUGS dprintf (1, ("gen%d: %Ix, %Ix(%Id), NON PIN alloc: %Id, pin com: %Id, sweep: %Id, surv: %Id, pinsurv: %Id(%d%% added, %d%% art), np surv: %Id, pad: %Id", gen_idx, generation_allocation_start (temp_gen), generation_plan_allocation_start (temp_gen), (size_t)(generation_plan_allocation_start (temp_gen) - generation_allocation_start (temp_gen)), generation_allocation_size (temp_gen), generation_pinned_allocation_compact_size (temp_gen), generation_pinned_allocation_sweep_size (temp_gen), dd_survived_size (temp_dd), dd_pinned_survived_size (temp_dd), added_pinning_ratio, artificial_pinned_ratio, (dd_survived_size (temp_dd) - dd_pinned_survived_size (temp_dd)), padding_size)); } #endif //SIMPLE_DPRINTF } // 繼續打印除錯信息,而且更新gen 2的統計信息 if (settings.condemned_generation == (max_generation - 1 )) { size_t plan_gen2_size = generation_plan_size (max_generation); size_t growth = plan_gen2_size - old_gen2_size; if (growth > 0) { dprintf (1, ("gen2 grew %Id (end seg alloc: %Id, gen1 c alloc: %Id", growth, generation_end_seg_allocated (generation_of (max_generation)), generation_condemned_allocated (generation_of (max_generation - 1)))); } else { dprintf (1, ("gen2 shrank %Id (end seg alloc: %Id, gen1 c alloc: %Id", (old_gen2_size - plan_gen2_size), generation_end_seg_allocated (generation_of (max_generation)), generation_condemned_allocated (generation_of (max_generation - 1)))); } generation* older_gen = generation_of (settings.condemned_generation + 1); size_t rejected_free_space = generation_free_obj_space (older_gen) - r_free_obj_space; size_t free_list_allocated = generation_free_list_allocated (older_gen) - r_older_gen_free_list_allocated; size_t end_seg_allocated = generation_end_seg_allocated (older_gen) - r_older_gen_end_seg_allocated; size_t condemned_allocated = generation_condemned_allocated (older_gen) - r_older_gen_condemned_allocated; dprintf (1, ("older gen's free alloc: %Id->%Id, seg alloc: %Id->%Id, condemned alloc: %Id->%Id", r_older_gen_free_list_allocated, generation_free_list_allocated (older_gen), r_older_gen_end_seg_allocated, generation_end_seg_allocated (older_gen), r_older_gen_condemned_allocated, generation_condemned_allocated (older_gen))); dprintf (1, ("this GC did %Id free list alloc(%Id bytes free space rejected), %Id seg alloc and %Id condemned alloc, gen1 condemned alloc is %Id", free_list_allocated, rejected_free_space, end_seg_allocated, condemned_allocated, generation_condemned_allocated (generation_of (settings.condemned_generation)))); maxgen_size_increase* maxgen_size_info = &(get_gc_data_per_heap()->maxgen_size_info); maxgen_size_info->free_list_allocated = free_list_allocated; maxgen_size_info->free_list_rejected = rejected_free_space; maxgen_size_info->end_seg_allocated = end_seg_allocated; maxgen_size_info->condemned_allocated = condemned_allocated; maxgen_size_info->pinned_allocated = maxgen_pinned_compact_before_advance; maxgen_size_info->pinned_allocated_advance = generation_pinned_allocation_compact_size (generation_of (max_generation)) - maxgen_pinned_compact_before_advance; #ifdef FREE_USAGE_STATS int free_list_efficiency = 0; if ((free_list_allocated + rejected_free_space) != 0) free_list_efficiency = (int)(((float) (free_list_allocated) / (float)(free_list_allocated + rejected_free_space)) * (float)100); int running_free_list_efficiency = (int)(generation_allocator_efficiency(older_gen)*100); dprintf (1, ("gen%d free list alloc effi: %d%%, current effi: %d%%", older_gen->gen_num, free_list_efficiency, running_free_list_efficiency)); dprintf (1, ("gen2 free list change")); for (int j = 0; j < NUM_GEN_POWER2; j++) { dprintf (1, ("[h%d][#%Id]: 2^%d: F: %Id->%Id(%Id), P: %Id", heap_number, settings.gc_index, (j + 10), r_older_gen_free_space[j], older_gen->gen_free_spaces[j], (ptrdiff_t)(r_older_gen_free_space[j] - older_gen->gen_free_spaces[j]), (generation_of(max_generation - 1))->gen_plugs[j])); } #endif //FREE_USAGE_STATS } // 計算碎片空間大小fragmentation // 這個是判斷是否要壓縮的依據之一 // 算法簡略以下 // frag = (heap_segment_allocated(ephemeral_heap_segment) - generation_allocation_pointer (consing_gen)) // for segment in non_ephemeral_segments // frag += heap_segment_allocated (seg) - heap_segment_plan_allocated (seg) // for plug in dequed_plugs // frag += plug.len size_t fragmentation = generation_fragmentation (generation_of (condemned_gen_number), consing_gen, heap_segment_allocated (ephemeral_heap_segment)); dprintf (2,("Fragmentation: %Id", fragmentation)); dprintf (2,("---- End of Plan phase ----")); // 統計計劃階段的結束時間 #ifdef TIME_GC finish = GetCycleCount32(); plan_time = finish - start; #endif //TIME_GC // We may update write barrier code. We assume here EE has been suspended if we are on a GC thread. assert(GCHeap::IsGCInProgress()); // 是否要擴展(使用新的segment heap segment) BOOL should_expand = FALSE; // 是否要壓縮 BOOL should_compact= FALSE; ephemeral_promotion = FALSE; // 若是內存過小應該強制開啓壓縮 #ifdef BIT64 if ((!settings.concurrent) && ((condemned_gen_number < max_generation) && ((settings.gen0_reduction_count > 0) || (settings.entry_memory_load >= 95)))) { dprintf (2, ("gen0 reduction count is %d, condemning %d, mem load %d", settings.gen0_reduction_count, condemned_gen_number, settings.entry_memory_load)); should_compact = TRUE; get_gc_data_per_heap()->set_mechanism (gc_heap_compact, ((settings.gen0_reduction_count > 0) ? compact_fragmented_gen0 : compact_high_mem_load)); // 若是ephemeal heap segment空間較少應該換一個新的segment if ((condemned_gen_number >= (max_generation - 1)) && dt_low_ephemeral_space_p (tuning_deciding_expansion)) { dprintf (2, ("Not enough space for all ephemeral generations with compaction")); should_expand = TRUE; } } else { #endif // BIT64 // 判斷是否要壓縮 // 請看下面函數decide_on_compacting的代碼解釋 should_compact = decide_on_compacting (condemned_gen_number, fragmentation, should_expand); #ifdef BIT64 } #endif // BIT64 // 判斷是否要壓縮大對象的堆 #ifdef FEATURE_LOH_COMPACTION loh_compacted_p = FALSE; #endif //FEATURE_LOH_COMPACTION if (condemned_gen_number == max_generation) { #ifdef FEATURE_LOH_COMPACTION if (settings.loh_compaction) { // 針對大對象的堆模擬壓縮,和前面建立plug計算reloc的處理差很少,可是一個plug中只有一個對象,也不會有plug樹 // 保存plug信息使用的類型是loh_obj_and_pad if (plan_loh()) { should_compact = TRUE; get_gc_data_per_heap()->set_mechanism (gc_heap_compact, compact_loh_forced); loh_compacted_p = TRUE; } } else { // 清空loh_pinned_queue if ((heap_number == 0) && (loh_pinned_queue)) { loh_pinned_queue_decay--; if (!loh_pinned_queue_decay) { delete loh_pinned_queue; loh_pinned_queue = 0; } } } // 若是不須要壓縮大對象的堆,在這裏執行清掃 // 把未標記的對象合併到一個free object而且加到free list中 // 請參考後面sweep phase的代碼解釋 if (!loh_compacted_p) #endif //FEATURE_LOH_COMPACTION { #if defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE) if (ShouldTrackMovementForProfilerOrEtw()) notify_profiler_of_surviving_large_objects(); #endif // defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE) sweep_large_objects(); } } else { settings.loh_compaction = FALSE; } #ifdef MULTIPLE_HEAPS // 若是存在多個heap(服務器GC)還須要投票從新決定should_compact和should_expand // 這裏的一些處理(例如刪除大對象segment和設置settings.demotion)是服務器GC和工做站GC都會作的 new_heap_segment = NULL; if (should_compact && should_expand) gc_policy = policy_expand; else if (should_compact) gc_policy = policy_compact; else gc_policy = policy_sweep; //vote for result of should_compact dprintf (3, ("Joining for compaction decision")); gc_t_join.join(this, gc_join_decide_on_compaction); if (gc_t_join.joined()) { // 刪除空的(無存活對象的)大對象segment //safe place to delete large heap segments if (condemned_gen_number == max_generation) { for (int i = 0; i < n_heaps; i++) { g_heaps [i]->rearrange_large_heap_segments (); } } settings.demotion = FALSE; int pol_max = policy_sweep; #ifdef GC_CONFIG_DRIVEN BOOL is_compaction_mandatory = FALSE; #endif //GC_CONFIG_DRIVEN int i; for (i = 0; i < n_heaps; i++) { if (pol_max < g_heaps[i]->gc_policy) pol_max = policy_compact; // set the demotion flag is any of the heap has demotion if (g_heaps[i]->demotion_high >= g_heaps[i]->demotion_low) { (g_heaps[i]->get_gc_data_per_heap())->set_mechanism_bit (gc_demotion_bit); settings.demotion = TRUE; } #ifdef GC_CONFIG_DRIVEN if (!is_compaction_mandatory) { int compact_reason = (g_heaps[i]->get_gc_data_per_heap())->get_mechanism (gc_heap_compact); if (compact_reason >= 0) { if (gc_heap_compact_reason_mandatory_p[compact_reason]) is_compaction_mandatory = TRUE; } } #endif //GC_CONFIG_DRIVEN } #ifdef GC_CONFIG_DRIVEN if (!is_compaction_mandatory) { // If compaction is not mandatory we can feel free to change it to a sweeping GC. // Note that we may want to change this to only checking every so often instead of every single GC. if (should_do_sweeping_gc (pol_max >= policy_compact)) { pol_max = policy_sweep; } else { if (pol_max == policy_sweep) pol_max = policy_compact; } } #endif //GC_CONFIG_DRIVEN for (i = 0; i < n_heaps; i++) { if (pol_max > g_heaps[i]->gc_policy) g_heaps[i]->gc_policy = pol_max; //get the segment while we are serialized if (g_heaps[i]->gc_policy == policy_expand) { g_heaps[i]->new_heap_segment = g_heaps[i]->soh_get_segment_to_expand(); if (!g_heaps[i]->new_heap_segment) { set_expand_in_full_gc (condemned_gen_number); //we are out of memory, cancel the expansion g_heaps[i]->gc_policy = policy_compact; } } } BOOL is_full_compacting_gc = FALSE; if ((gc_policy >= policy_compact) && (condemned_gen_number == max_generation)) { full_gc_counts[gc_type_compacting]++; is_full_compacting_gc = TRUE; } for (i = 0; i < n_heaps; i++) { //copy the card and brick tables if (g_card_table!= g_heaps[i]->card_table) { g_heaps[i]->copy_brick_card_table(); } if (is_full_compacting_gc) { g_heaps[i]->loh_alloc_since_cg = 0; } } //start all threads on the roots. dprintf(3, ("Starting all gc threads after compaction decision")); gc_t_join.restart(); } //reset the local variable accordingly should_compact = (gc_policy >= policy_compact); should_expand = (gc_policy >= policy_expand); #else //MULTIPLE_HEAPS // 刪除空的(無存活對象的)大對象segment //safe place to delete large heap segments if (condemned_gen_number == max_generation) { rearrange_large_heap_segments (); } // 若是有對象被降代,則設置settings.demotion = true settings.demotion = ((demotion_high >= demotion_low) ? TRUE : FALSE); if (settings.demotion) get_gc_data_per_heap()->set_mechanism_bit (gc_demotion_bit); // 若是壓縮不是必須的,根據用戶提供的特殊設置從新設置should_compact #ifdef GC_CONFIG_DRIVEN BOOL is_compaction_mandatory = FALSE; int compact_reason = get_gc_data_per_heap()->get_mechanism (gc_heap_compact); if (compact_reason >= 0) is_compaction_mandatory = gc_heap_compact_reason_mandatory_p[compact_reason]; if (!is_compaction_mandatory) { if (should_do_sweeping_gc (should_compact)) should_compact = FALSE; else should_compact = TRUE; } #endif //GC_CONFIG_DRIVEN if (should_compact && (condemned_gen_number == max_generation)) { full_gc_counts[gc_type_compacting]++; loh_alloc_since_cg = 0; } #endif //MULTIPLE_HEAPS // 進入重定位和壓縮階段 if (should_compact) { dprintf (2,( "**** Doing Compacting GC ****")); // 若是應該使用新的ephemeral heap segment,調用expand_heap // expand_heap有可能會複用前面的segment,也有可能從新生成一個segment if (should_expand) { #ifndef MULTIPLE_HEAPS heap_segment* new_heap_segment = soh_get_segment_to_expand(); #endif //!MULTIPLE_HEAPS if (new_heap_segment) { consing_gen = expand_heap(condemned_gen_number, consing_gen, new_heap_segment); } // If we couldn't get a new segment, or we were able to // reserve one but no space to commit, we couldn't // expand heap. if (ephemeral_heap_segment != new_heap_segment) { set_expand_in_full_gc (condemned_gen_number); should_expand = FALSE; } } generation_allocation_limit (condemned_gen1) = generation_allocation_pointer (condemned_gen1); if ((condemned_gen_number < max_generation)) { generation_allocator (older_gen)->commit_alloc_list_changes(); // 若是 generation_allocation_limit 等於 heap_segment_plan_allocated // 設置 heap_segment_plan_allocated 等於 generation_allocation_pointer // 設置 generation_allocation_limit 等於 generation_allocation_pointer // 不然 // 在alloc_ptr到limit的空間建立一個free object, 不加入free list // Fix the allocation area of the older generation fix_older_allocation_area (older_gen); } assert (generation_allocation_segment (consing_gen) == ephemeral_heap_segment); #if defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE) if (ShouldTrackMovementForProfilerOrEtw()) { record_survived_for_profiler(condemned_gen_number, first_condemned_address); } #endif // defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE) // 調用重定位階段 // 這裏會修改全部須要移動的對象的指針地址,可是不會移動它們的內容 // 具體代碼請看後面 relocate_phase (condemned_gen_number, first_condemned_address); // 調用壓縮階段 // 這裏會複製對象的內容到它們移動到的地址 // 具體代碼請看後面 compact_phase (condemned_gen_number, first_condemned_address, (!settings.demotion && settings.promotion)); // fix_generation_bounds作的事情以下 // - 應用各個代的計劃代邊界 // - generation_allocation_start (gen) = generation_plan_allocation_start (gen) // - generation_allocation_pointer (gen) = 0; // - generation_allocation_limit (gen) = 0; // - 代邊界的開始會留一段min_obj_size的空間,把這段空間變爲free object // - 若是ephemeral segment已改變則設置舊ephemeral segment的start到allocated的整個範圍到Card Table // - 設置ephemeral_heap_segment的allocated到plan_allocated fix_generation_bounds (condemned_gen_number, consing_gen); assert (generation_allocation_limit (youngest_generation) == generation_allocation_pointer (youngest_generation)); // 刪除空的(無存活對象的)小對象segment // 修復segment鏈表,若是ephemeral heap segment由於expand_heap改變了這裏會從新正確的連接各個segment // 修復segment的處理 // - 若是segment的next是null且堆段不是ephemeral segment, 則next = ephemeral segment // - 若是segment是ephemeral_heap_segment而且有next, 則單獨把這個segment抽出來(prev.next = next) // - 調用delete_heap_segment刪除無存活對象的segment // - 設置heap_segment_allocated (seg) = heap_segment_plan_allocated (seg) // - 若是segment不是ephemeral segment, 則調用decommit_heap_segment_pages釋放allocated到committed的內存 if (condemned_gen_number >= (max_generation -1)) { #ifdef MULTIPLE_HEAPS // this needs be serialized just because we have one // segment_standby_list/seg_table for all heaps. We should make it at least // so that when hoarding is not on we don't need this join because // decommitting memory can take a long time. //must serialize on deleting segments gc_t_join.join(this, gc_join_rearrange_segs_compaction); if (gc_t_join.joined()) { for (int i = 0; i < n_heaps; i++) { g_heaps[i]->rearrange_heap_segments(TRUE); } gc_t_join.restart(); } #else rearrange_heap_segments(TRUE); #endif //MULTIPLE_HEAPS // 從新設置第0代和第1代的generation_start_segment和generation_allocation_segment到新的ephemeral_heap_segment if (should_expand) { //fix the start_segment for the ephemeral generations for (int i = 0; i < max_generation; i++) { generation* gen = generation_of (i); generation_start_segment (gen) = ephemeral_heap_segment; generation_allocation_segment (gen) = ephemeral_heap_segment; } } } { // 由於析構隊列中的對象分代儲存,這裏根據升代或者降代移動析構隊列中的對象 #ifdef FEATURE_PREMORTEM_FINALIZATION finalize_queue->UpdatePromotedGenerations (condemned_gen_number, (!settings.demotion && settings.promotion)); #endif // FEATURE_PREMORTEM_FINALIZATION #ifdef MULTIPLE_HEAPS dprintf(3, ("Joining after end of compaction")); gc_t_join.join(this, gc_join_adjust_handle_age_compact); if (gc_t_join.joined()) #endif //MULTIPLE_HEAPS { #ifdef MULTIPLE_HEAPS //join all threads to make sure they are synchronized dprintf(3, ("Restarting after Promotion granted")); gc_t_join.restart(); #endif //MULTIPLE_HEAPS } // 更新GC Handle表中記錄的代數 // GcPromotionsGranted的處理: // 調用 Ref_AgeHandles(condemned, max_gen, (uintptr_t)sc) // GcDemote的處理: // 調用 Ref_RejuvenateHandles (condemned, max_gen, (uintptr_t)sc) // Ref_AgeHandles的處理: // 掃描g_HandleTableMap中的HandleTable, 逐個調用 BlockAgeBlocks // BlockAgeBlocks會增長rgGeneration+uBlock~uCount中的數字 // 0x00ffffff => 0x01ffffff => 0x02ffffff // #define COMPUTE_AGED_CLUMPS(gen, msk) APPLY_CLUMP_ADDENDS(gen, COMPUTE_CLUMP_ADDENDS(gen, msk)) // #define COMPUTE_AGED_CLUMPS(gen, msk) gen + COMPUTE_CLUMP_ADDENDS(gen, msk) // #define COMPUTE_AGED_CLUMPS(gen, msk) gen + MAKE_CLUMP_MASK_ADDENDS(COMPUTE_CLUMP_MASK(gen, msk)) // #define COMPUTE_AGED_CLUMPS(gen, msk) gen + MAKE_CLUMP_MASK_ADDENDS((((gen & GEN_CLAMP) - msk) & GEN_MASK)) // #define COMPUTE_AGED_CLUMPS(gen, msk) gen + (((((gen & GEN_CLAMP) - msk) & GEN_MASK)) >> GEN_INC_SHIFT) // #define COMPUTE_AGED_CLUMPS(gen, msk) gen + (((((gen & 0x3F3F3F3F) - msk) & 0x40404040)) >> 6) // #define GEN_FULLGC PREFOLD_FILL_INTO_AGEMASK(GEN_AGE_LIMIT) // #define GEN_FULLGC PREFOLD_FILL_INTO_AGEMASK(0x3E3E3E3E) // #define GEN_FULLGC (1 + (0x3E3E3E3E) + (~GEN_FILL)) // #define GEN_FULLGC (1 + (0x3E3E3E3E) + (~0x80808080)) // #define GEN_FULLGC 0xbfbfbfbe // Ref_RejuvenateHandles的處理: // 掃描g_HandleTableMap中的HandleTable, 逐個調用 BlockResetAgeMapForBlocks // BlockAgeBlocks會減小rgGeneration+uBlock~uCount中的數字 // 取決於該block中的handle中最年輕的代數 // rgGeneration // 一個block對應4 byte, 第一個byte表明該block中的GCHandle的代 ScanContext sc; sc.thread_number = heap_number; sc.promotion = FALSE; sc.concurrent = FALSE; // new generations bounds are set can call this guy if (settings.promotion && !settings.demotion) { dprintf (2, ("Promoting EE roots for gen %d", condemned_gen_number)); GCScan::GcPromotionsGranted(condemned_gen_number, max_generation, &sc); } else if (settings.demotion) { dprintf (2, ("Demoting EE roots for gen %d", condemned_gen_number)); GCScan::GcDemote (condemned_gen_number, max_generation, &sc); } } // 把各個pinned plug前面的空餘空間(出隊後的len)變爲free object並加到free list中 { gen0_big_free_spaces = 0; // 隊列底部等於0 reset_pinned_queue_bos(); unsigned int gen_number = min (max_generation, 1 + condemned_gen_number); generation* gen = generation_of (gen_number); uint8_t* low = generation_allocation_start (generation_of (gen_number-1)); uint8_t* high = heap_segment_allocated (ephemeral_heap_segment); while (!pinned_plug_que_empty_p()) { // 出隊 mark* m = pinned_plug_of (deque_pinned_plug()); size_t len = pinned_len (m); uint8_t* arr = (pinned_plug (m) - len); dprintf(3,("free [%Ix %Ix[ pin", (size_t)arr, (size_t)arr + len)); if (len != 0) { // 在pinned plug前的空餘空間建立free object assert (len >= Align (min_obj_size)); make_unused_array (arr, len); // fix fully contained bricks + first one // if the array goes beyong the first brick size_t start_brick = brick_of (arr); size_t end_brick = brick_of (arr + len); // 若是free object橫跨多個brick,更新brick表 if (end_brick != start_brick) { dprintf (3, ("Fixing bricks [%Ix, %Ix[ to point to unused array %Ix", start_brick, end_brick, (size_t)arr)); set_brick (start_brick, arr - brick_address (start_brick)); size_t brick = start_brick+1; while (brick < end_brick) { set_brick (brick, start_brick - brick); brick++; } } // 判斷要加到哪一個代的free list中 //when we take an old segment to make the new //ephemeral segment. we can have a bunch of //pinned plugs out of order going to the new ephemeral seg //and then the next plugs go back to max_generation if ((heap_segment_mem (ephemeral_heap_segment) <= arr) && (heap_segment_reserved (ephemeral_heap_segment) > arr)) { while ((low <= arr) && (high > arr)) { gen_number--; assert ((gen_number >= 1) || (demotion_low != MAX_PTR) || settings.demotion || !settings.promotion); dprintf (3, ("new free list generation %d", gen_number)); gen = generation_of (gen_number); if (gen_number >= 1) low = generation_allocation_start (generation_of (gen_number-1)); else low = high; } } else { dprintf (3, ("new free list generation %d", max_generation)); gen_number = max_generation; gen = generation_of (gen_number); } // 加到free list中 dprintf(3,("threading it into generation %d", gen_number)); thread_gap (arr, len, gen); add_gen_free (gen_number, len); } } } #ifdef _DEBUG for (int x = 0; x <= max_generation; x++) { assert (generation_allocation_start (generation_of (x))); } #endif //_DEBUG // 若是已經升代了,原來gen 0的對象會變爲gen 1 // 清理當前gen 1在Card Table中的標記 if (!settings.demotion && settings.promotion) { //clear card for generation 1. generation 0 is empty clear_card_for_addresses ( generation_allocation_start (generation_of (1)), generation_allocation_start (generation_of (0))); } // 若是已經升代了,確認代0的只包含一個對象(一個最小大小的free object) if (settings.promotion && !settings.demotion) { uint8_t* start = generation_allocation_start (youngest_generation); MAYBE_UNUSED_VAR(start); assert (heap_segment_allocated (ephemeral_heap_segment) == (start + Align (size (start)))); } } // 進入清掃階段 // 清掃階段的關鍵處理在make_free_lists中,目前你看不到叫`sweep_phase`的函數,這裏就是sweep phase else { // 清掃階段必須升代 //force promotion for sweep settings.promotion = TRUE; settings.compaction = FALSE; ScanContext sc; sc.thread_number = heap_number; sc.promotion = FALSE; sc.concurrent = FALSE; dprintf (2, ("**** Doing Mark and Sweep GC****")); // 恢復對舊代成員的備份 if ((condemned_gen_number < max_generation)) { generation_allocator (older_gen)->copy_from_alloc_list (r_free_list); generation_free_list_space (older_gen) = r_free_list_space; generation_free_obj_space (older_gen) = r_free_obj_space; generation_free_list_allocated (older_gen) = r_older_gen_free_list_allocated; generation_end_seg_allocated (older_gen) = r_older_gen_end_seg_allocated; generation_condemned_allocated (older_gen) = r_older_gen_condemned_allocated; generation_allocation_limit (older_gen) = r_allocation_limit; generation_allocation_pointer (older_gen) = r_allocation_pointer; generation_allocation_context_start_region (older_gen) = r_allocation_start_region; generation_allocation_segment (older_gen) = r_allocation_segment; } // 若是 generation_allocation_limit 等於 heap_segment_plan_allocated // 設置 heap_segment_plan_allocated 等於 generation_allocation_pointer // 設置 generation_allocation_limit 等於 generation_allocation_pointer // 不然 // 在alloc_ptr到limit的空間建立一個free object, 不加入free list if ((condemned_gen_number < max_generation)) { // Fix the allocation area of the older generation fix_older_allocation_area (older_gen); } #if defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE) if (ShouldTrackMovementForProfilerOrEtw()) { record_survived_for_profiler(condemned_gen_number, first_condemned_address); } #endif // defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE) // 把不使用的空間變爲free object並存到free list gen0_big_free_spaces = 0; make_free_lists (condemned_gen_number); // 恢復在saved_pre_plug和saved_post_plug保存的原始數據 recover_saved_pinned_info(); // 由於析構隊列中的對象分代儲存,這裏根據升代或者降代移動析構隊列中的對象 #ifdef FEATURE_PREMORTEM_FINALIZATION finalize_queue->UpdatePromotedGenerations (condemned_gen_number, TRUE); #endif // FEATURE_PREMORTEM_FINALIZATION // MTHTS: leave single thread for HT processing on plan_phase #ifdef MULTIPLE_HEAPS dprintf(3, ("Joining after end of sweep")); gc_t_join.join(this, gc_join_adjust_handle_age_sweep); if (gc_t_join.joined()) #endif //MULTIPLE_HEAPS { // 更新GCHandle表中記錄的代數 GCScan::GcPromotionsGranted(condemned_gen_number, max_generation, &sc); // 刪除空的(無存活對象的)小對象segment和修復segment鏈表 // 上面有詳細的註釋 if (condemned_gen_number >= (max_generation -1)) { #ifdef MULTIPLE_HEAPS for (int i = 0; i < n_heaps; i++) { g_heaps[i]->rearrange_heap_segments(FALSE); } #else rearrange_heap_segments(FALSE); #endif //MULTIPLE_HEAPS } #ifdef MULTIPLE_HEAPS //join all threads to make sure they are synchronized dprintf(3, ("Restarting after Promotion granted")); gc_t_join.restart(); #endif //MULTIPLE_HEAPS } #ifdef _DEBUG for (int x = 0; x <= max_generation; x++) { assert (generation_allocation_start (generation_of (x))); } #endif //_DEBUG // 由於已經升代了,原來gen 0的對象會變爲gen 1 // 清理當前gen 1在Card Table中的標記 //clear card for generation 1. generation 0 is empty clear_card_for_addresses ( generation_allocation_start (generation_of (1)), generation_allocation_start (generation_of (0))); assert ((heap_segment_allocated (ephemeral_heap_segment) == (generation_allocation_start (youngest_generation) + Align (min_obj_size)))); } //verify_partial(); }
process_ephemeral_boundaries
函數的代碼:
若是當前模擬的segment是ephemeral heap segment,這個函數會在模擬當前plug的壓縮前調用決定計劃代邊界
void gc_heap::process_ephemeral_boundaries (uint8_t* x, int& active_new_gen_number, int& active_old_gen_number, generation*& consing_gen, BOOL& allocate_in_condemned) { retry: // 判斷是否要設置計劃代邊界 // 例如當前啓用升代 // - active_old_gen_number是1,active_new_gen_number是2 // - 判斷plug屬於gen 0的時候會計劃gen 1(active_new_gen_number--)的邊界 // 例如當前不啓用升代 // - active_old_gen_number是1,active_new_gen_number是1 // - 判斷plug屬於gen 0的時候會計劃gen 0(active_new_gen_number--)的邊界 if ((active_old_gen_number > 0) && (x >= generation_allocation_start (generation_of (active_old_gen_number - 1)))) { dprintf (1, ("crossing gen%d, x is %Ix", active_old_gen_number - 1, x)); if (!pinned_plug_que_empty_p()) { dprintf (1, ("oldest pin: %Ix(%Id)", pinned_plug (oldest_pin()), (x - pinned_plug (oldest_pin())))); } // 若是升代 // active_old_gen_number: 2 => 1 => 0 // active_new_gen_number: 2 => 2 => 1 // 若是不升代 // active_old_gen_number: 2 => 1 => 0 // active_new_gen_number: 2 => 1 => 0 if (active_old_gen_number <= (settings.promotion ? (max_generation - 1) : max_generation)) { active_new_gen_number--; } active_old_gen_number--; assert ((!settings.promotion) || (active_new_gen_number>0)); if (active_new_gen_number == (max_generation - 1)) { // 打印和設置統計信息 #ifdef FREE_USAGE_STATS if (settings.condemned_generation == max_generation) { // We need to do this before we skip the rest of the pinned plugs. generation* gen_2 = generation_of (max_generation); generation* gen_1 = generation_of (max_generation - 1); size_t total_num_pinned_free_spaces_left = 0; // We are about to allocate gen1, check to see how efficient fitting in gen2 pinned free spaces is. for (int j = 0; j < NUM_GEN_POWER2; j++) { dprintf (1, ("[h%d][#%Id]2^%d: current: %Id, S: 2: %Id, 1: %Id(%Id)", heap_number, settings.gc_index, (j + 10), gen_2->gen_current_pinned_free_spaces[j], gen_2->gen_plugs[j], gen_1->gen_plugs[j], (gen_2->gen_plugs[j] + gen_1->gen_plugs[j]))); total_num_pinned_free_spaces_left += gen_2->gen_current_pinned_free_spaces[j]; } float pinned_free_list_efficiency = 0; size_t total_pinned_free_space = generation_allocated_in_pinned_free (gen_2) + generation_pinned_free_obj_space (gen_2); if (total_pinned_free_space != 0) { pinned_free_list_efficiency = (float)(generation_allocated_in_pinned_free (gen_2)) / (float)total_pinned_free_space; } dprintf (1, ("[h%d] gen2 allocated %Id bytes with %Id bytes pinned free spaces (effi: %d%%), %Id (%Id) left", heap_number, generation_allocated_in_pinned_free (gen_2), total_pinned_free_space, (int)(pinned_free_list_efficiency * 100), generation_pinned_free_obj_space (gen_2), total_num_pinned_free_spaces_left)); } #endif //FREE_USAGE_STATS // 出隊mark_stack_array中不屬於ephemeral heap segment的pinned plug,不能讓它們降代 //Go past all of the pinned plugs for this generation. while (!pinned_plug_que_empty_p() && (!in_range_for_segment ((pinned_plug (oldest_pin())), ephemeral_heap_segment))) { size_t entry = deque_pinned_plug(); mark* m = pinned_plug_of (entry); uint8_t* plug = pinned_plug (m); size_t len = pinned_len (m); // detect pinned block in different segment (later) than // allocation segment, skip those until the oldest pin is in the ephemeral seg. // adjust the allocation segment along the way (at the end it will // be the ephemeral segment. heap_segment* nseg = heap_segment_in_range (generation_allocation_segment (consing_gen)); PREFIX_ASSUME(nseg != NULL); while (!((plug >= generation_allocation_pointer (consing_gen))&& (plug < heap_segment_allocated (nseg)))) { //adjust the end of the segment to be the end of the plug assert (generation_allocation_pointer (consing_gen)>= heap_segment_mem (nseg)); assert (generation_allocation_pointer (consing_gen)<= heap_segment_committed (nseg)); heap_segment_plan_allocated (nseg) = generation_allocation_pointer (consing_gen); //switch allocation segment nseg = heap_segment_next_rw (nseg); generation_allocation_segment (consing_gen) = nseg; //reset the allocation pointer and limits generation_allocation_pointer (consing_gen) = heap_segment_mem (nseg); } set_new_pin_info (m, generation_allocation_pointer (consing_gen)); assert(pinned_len(m) == 0 || pinned_len(m) >= Align(min_obj_size)); generation_allocation_pointer (consing_gen) = plug + len; generation_allocation_limit (consing_gen) = generation_allocation_pointer (consing_gen); } allocate_in_condemned = TRUE; consing_gen = ensure_ephemeral_heap_segment (consing_gen); } // active_new_gen_number不等於gen2的時候計劃它的邊界 // gen2的邊界不會在這裏計劃,而是在前面(allocate_first_generation_start) if (active_new_gen_number != max_generation) { // 防止降代的時候把全部pinned plug出隊 if (active_new_gen_number == (max_generation - 1)) { maxgen_pinned_compact_before_advance = generation_pinned_allocation_compact_size (generation_of (max_generation)); if (!demote_gen1_p) advance_pins_for_demotion (consing_gen); } // 根據當前的generaion_allocation_pointer(alloc_ptr)計劃代邊界 plan_generation_start (generation_of (active_new_gen_number), consing_gen, x); dprintf (1, ("process eph: allocated gen%d start at %Ix", active_new_gen_number, generation_plan_allocation_start (generation_of (active_new_gen_number)))); // 若是隊列中仍然有pinned plug if ((demotion_low == MAX_PTR) && !pinned_plug_que_empty_p()) { // 而且最老(最左邊)的pinned plug的代數不是0的時候 uint8_t* pplug = pinned_plug (oldest_pin()); if (object_gennum (pplug) > 0) { // 表示從這個pinned plug和後面的pinned plug都被降代了 // 設置降代範圍 demotion_low = pplug; dprintf (3, ("process eph: dlow->%Ix", demotion_low)); } } assert (generation_plan_allocation_start (generation_of (active_new_gen_number))); } goto retry; } }
gc_heap::plan_generation_start
函數的代碼以下:
根據當前的generaion_allocation_pointer(alloc_ptr)計劃代邊界
void gc_heap::plan_generation_start (generation* gen, generation* consing_gen, uint8_t* next_plug_to_allocate) { // 特殊處理 // 若是某些pinned plug很大(大於demotion_plug_len_th(6MB)),把它們出隊防止降代 #ifdef BIT64 // We should never demote big plugs to gen0. if (gen == youngest_generation) { heap_segment* seg = ephemeral_heap_segment; size_t mark_stack_large_bos = mark_stack_bos; size_t large_plug_pos = 0; while (mark_stack_large_bos < mark_stack_tos) { if (mark_stack_array[mark_stack_large_bos].len > demotion_plug_len_th) { while (mark_stack_bos <= mark_stack_large_bos) { size_t entry = deque_pinned_plug(); size_t len = pinned_len (pinned_plug_of (entry)); uint8_t* plug = pinned_plug (pinned_plug_of(entry)); if (len > demotion_plug_len_th) { dprintf (2, ("ps(%d): S %Ix (%Id)(%Ix)", gen->gen_num, plug, len, (plug+len))); } pinned_len (pinned_plug_of (entry)) = plug - generation_allocation_pointer (consing_gen); assert(mark_stack_array[entry].len == 0 || mark_stack_array[entry].len >= Align(min_obj_size)); generation_allocation_pointer (consing_gen) = plug + len; generation_allocation_limit (consing_gen) = heap_segment_plan_allocated (seg); set_allocator_next_pin (consing_gen); } } mark_stack_large_bos++; } } #endif // BIT64 // 在當前consing_gen的generation_allocation_ptr建立一個最小的對象 // 以這個對象的開始地址做爲計劃代邊界 // 這裏的處理是保證代與代之間最少有一個對象(初始化代的時候也會這樣保證) generation_plan_allocation_start (gen) = allocate_in_condemned_generations (consing_gen, Align (min_obj_size), -1); // 壓縮後會根據這個大小把這裏的空間變爲一個free object generation_plan_allocation_start_size (gen) = Align (min_obj_size); // 若是接下來的空間很小(小於min_obj_size),則把接下來的空間也加到上面的初始對象裏 size_t allocation_left = (size_t)(generation_allocation_limit (consing_gen) - generation_allocation_pointer (consing_gen)); if (next_plug_to_allocate) { size_t dist_to_next_plug = (size_t)(next_plug_to_allocate - generation_allocation_pointer (consing_gen)); if (allocation_left > dist_to_next_plug) { allocation_left = dist_to_next_plug; } } if (allocation_left < Align (min_obj_size)) { generation_plan_allocation_start_size (gen) += allocation_left; generation_allocation_pointer (consing_gen) += allocation_left; } dprintf (1, ("plan alloc gen%d(%Ix) start at %Ix (ptr: %Ix, limit: %Ix, next: %Ix)", gen->gen_num, generation_plan_allocation_start (gen), generation_plan_allocation_start_size (gen), generation_allocation_pointer (consing_gen), generation_allocation_limit (consing_gen), next_plug_to_allocate)); }
gc_heap::plan_generation_starts
函數的代碼以下:
這個函數會在模擬壓縮全部對象後調用,用於計劃剩餘的代邊界,若是啓用了升代這裏會計劃gen 0的邊界
void gc_heap::plan_generation_starts (generation*& consing_gen) { //make sure that every generation has a planned allocation start int gen_number = settings.condemned_generation; while (gen_number >= 0) { // 由於不能把gen 1和gen 0的邊界放到其餘segment中 // 這裏須要確保consing_gen的allocation segment是ephemeral heap segment if (gen_number < max_generation) { consing_gen = ensure_ephemeral_heap_segment (consing_gen); } // 若是這個代的邊界還沒有計劃,則執行計劃 generation* gen = generation_of (gen_number); if (0 == generation_plan_allocation_start (gen)) { plan_generation_start (gen, consing_gen, 0); assert (generation_plan_allocation_start (gen)); } gen_number--; } // 設置ephemeral heap segment的計劃已分配大小 // now we know the planned allocation size heap_segment_plan_allocated (ephemeral_heap_segment) = generation_allocation_pointer (consing_gen); }
gc_heap::generation_fragmentation
函數的代碼以下:
size_t gc_heap::generation_fragmentation (generation* gen, generation* consing_gen, uint8_t* end) { size_t frag; // 判斷是否全部對象都壓縮到了ephemeral heap segment以前 uint8_t* alloc = generation_allocation_pointer (consing_gen); // If the allocation pointer has reached the ephemeral segment // fine, otherwise the whole ephemeral segment is considered // fragmentation if (in_range_for_segment (alloc, ephemeral_heap_segment)) { // 原allocated - 模擬壓縮的結尾allocation_pointer if (alloc <= heap_segment_allocated(ephemeral_heap_segment)) frag = end - alloc; else { // 無一個對象存活,已經把allocated設到開始地址 // case when no survivors, allocated set to beginning frag = 0; } dprintf (3, ("ephemeral frag: %Id", frag)); } else // 全部對象都壓縮到了ephemeral heap segment以前 // 添加整個範圍到frag frag = (heap_segment_allocated (ephemeral_heap_segment) - heap_segment_mem (ephemeral_heap_segment)); heap_segment* seg = heap_segment_rw (generation_start_segment (gen)); PREFIX_ASSUME(seg != NULL); // 添加其餘segment的原allocated - 計劃allcated while (seg != ephemeral_heap_segment) { frag += (heap_segment_allocated (seg) - heap_segment_plan_allocated (seg)); dprintf (3, ("seg: %Ix, frag: %Id", (size_t)seg, (heap_segment_allocated (seg) - heap_segment_plan_allocated (seg)))); seg = heap_segment_next_rw (seg); assert (seg); } // 添加全部pinned plug前面的空餘空間 dprintf (3, ("frag: %Id discounting pinned plugs", frag)); //add the length of the dequeued plug free space size_t bos = 0; while (bos < mark_stack_bos) { frag += (pinned_len (pinned_plug_of (bos))); bos++; } return frag; }
gc_heap::decide_on_compacting
函數的代碼以下:
BOOL gc_heap::decide_on_compacting (int condemned_gen_number, size_t fragmentation, BOOL& should_expand) { BOOL should_compact = FALSE; should_expand = FALSE; generation* gen = generation_of (condemned_gen_number); dynamic_data* dd = dynamic_data_of (condemned_gen_number); size_t gen_sizes = generation_sizes(gen); // 碎片空間大小 / 收集代的大小(包括更年輕的代) float fragmentation_burden = ( ((0 == fragmentation) || (0 == gen_sizes)) ? (0.0f) : (float (fragmentation) / gen_sizes) ); dprintf (GTC_LOG, ("fragmentation: %Id (%d%%)", fragmentation, (int)(fragmentation_burden * 100.0))); // 由Stress GC決定是否壓縮 #ifdef STRESS_HEAP // for pure GC stress runs we need compaction, for GC stress "mix" // we need to ensure a better mix of compacting and sweeping collections if (GCStress<cfg_any>::IsEnabled() && !settings.concurrent && !g_pConfig->IsGCStressMix()) should_compact = TRUE; // 由Stress GC決定是否壓縮 // 若是壓縮次數不夠清掃次數的十分之一則開啓壓縮 #ifdef GC_STATS // in GC stress "mix" mode, for stress induced collections make sure we // keep sweeps and compactions relatively balanced. do not (yet) force sweeps // against the GC's determination, as it may lead to premature OOMs. if (g_pConfig->IsGCStressMix() && settings.stress_induced) { int compactions = g_GCStatistics.cntCompactFGC+g_GCStatistics.cntCompactNGC; int sweeps = g_GCStatistics.cntFGC + g_GCStatistics.cntNGC - compactions; if (compactions < sweeps / 10) { should_compact = TRUE; } } #endif // GC_STATS #endif //STRESS_HEAP // 判斷是否強制壓縮 if (g_pConfig->GetGCForceCompact()) should_compact = TRUE; // 是否由於OOM(Out Of Memory)致使的GC,若是是則開啓壓縮 if ((condemned_gen_number == max_generation) && last_gc_before_oom) { should_compact = TRUE; last_gc_before_oom = FALSE; get_gc_data_per_heap()->set_mechanism (gc_heap_compact, compact_last_gc); } // gc緣由中有壓縮 if (settings.reason == reason_induced_compacting) { dprintf (2, ("induced compacting GC")); should_compact = TRUE; get_gc_data_per_heap()->set_mechanism (gc_heap_compact, compact_induced_compacting); } dprintf (2, ("Fragmentation: %d Fragmentation burden %d%%", fragmentation, (int) (100*fragmentation_burden))); // 若是ephemeral heap segment的空間較少則開啓壓縮 if (!should_compact) { if (dt_low_ephemeral_space_p (tuning_deciding_compaction)) { dprintf(GTC_LOG, ("compacting due to low ephemeral")); should_compact = TRUE; get_gc_data_per_heap()->set_mechanism (gc_heap_compact, compact_low_ephemeral); } } // 若是ephemeral heap segment的空間較少,而且當前不是Full GC還須要使用新的ephemeral heap segment if (should_compact) { if ((condemned_gen_number >= (max_generation - 1))) { if (dt_low_ephemeral_space_p (tuning_deciding_expansion)) { dprintf (GTC_LOG,("Not enough space for all ephemeral generations with compaction")); should_expand = TRUE; } } } #ifdef BIT64 BOOL high_memory = FALSE; #endif // BIT64 // 根據碎片空間大小判斷 if (!should_compact) { // We are not putting this in dt_high_frag_p because it's not exactly // high fragmentation - it's just enough planned fragmentation for us to // want to compact. Also the "fragmentation" we are talking about here // is different from anywhere else. // 碎片空間大小 >= dd_fragmentation_limit 或者 // 碎片空間大小 / 收集代的大小(包括更年輕的代) >= dd_fragmentation_burden_limit 時開啓壓縮 // 做者機器上的dd_fragmentation_limit是200000, dd_fragmentation_burden_limit是0.25 BOOL frag_exceeded = ((fragmentation >= dd_fragmentation_limit (dd)) && (fragmentation_burden >= dd_fragmentation_burden_limit (dd))); if (frag_exceeded) { #ifdef BACKGROUND_GC // do not force compaction if this was a stress-induced GC IN_STRESS_HEAP(if (!settings.stress_induced)) { #endif // BACKGROUND_GC assert (settings.concurrent == FALSE); should_compact = TRUE; get_gc_data_per_heap()->set_mechanism (gc_heap_compact, compact_high_frag); #ifdef BACKGROUND_GC } #endif // BACKGROUND_GC } // 若是佔用內存太高則啓用壓縮 #ifdef BIT64 // check for high memory situation if(!should_compact) { uint32_t num_heaps = 1; #ifdef MULTIPLE_HEAPS num_heaps = gc_heap::n_heaps; #endif // MULTIPLE_HEAPS ptrdiff_t reclaim_space = generation_size(max_generation) - generation_plan_size(max_generation); if((settings.entry_memory_load >= high_memory_load_th) && (settings.entry_memory_load < v_high_memory_load_th)) { if(reclaim_space > (int64_t)(min_high_fragmentation_threshold (entry_available_physical_mem, num_heaps))) { dprintf(GTC_LOG,("compacting due to fragmentation in high memory")); should_compact = TRUE; get_gc_data_per_heap()->set_mechanism (gc_heap_compact, compact_high_mem_frag); } high_memory = TRUE; } else if(settings.entry_memory_load >= v_high_memory_load_th) { if(reclaim_space > (ptrdiff_t)(min_reclaim_fragmentation_threshold (num_heaps))) { dprintf(GTC_LOG,("compacting due to fragmentation in very high memory")); should_compact = TRUE; get_gc_data_per_heap()->set_mechanism (gc_heap_compact, compact_vhigh_mem_frag); } high_memory = TRUE; } } #endif // BIT64 } // 測試是否能夠在ephemeral_heap_segment.allocated後面提交一段內存(從系統獲取一塊物理內存) // 若是失敗則啓用壓縮 allocated (ephemeral_heap_segment); size_t size = Align (min_obj_size)*(condemned_gen_number+1); // The purpose of calling ensure_gap_allocation here is to make sure // that we actually are able to commit the memory to allocate generation // starts. if ((should_compact == FALSE) && (ensure_gap_allocation (condemned_gen_number) == FALSE)) { should_compact = TRUE; get_gc_data_per_heap()->set_mechanism (gc_heap_compact, compact_no_gaps); } // 若是此次Full GC的效果比較差 // 須要減小Full GC的頻率,should_lock_elevation能夠把Full GC變爲gen 1 GC if (settings.condemned_generation == max_generation) { //check the progress if ( #ifdef BIT64 (high_memory && !should_compact) || #endif // BIT64 (generation_plan_allocation_start (generation_of (max_generation - 1)) >= generation_allocation_start (generation_of (max_generation - 1)))) { dprintf (2, (" Elevation: gen2 size: %d, gen2 plan size: %d, no progress, elevation = locked", generation_size (max_generation), generation_plan_size (max_generation))); //no progress -> lock settings.should_lock_elevation = TRUE; } } // 若是啓用了NoGCRegion可是仍然啓用了GC表明這是沒法從SOH(Small Object Heap)或者LOH分配到內存致使的,須要啓用壓縮 if (settings.pause_mode == pause_no_gc) { should_compact = TRUE; // 若是ephemeral heap segement壓縮後的剩餘空間不足還須要設置新的ephemeral heap segment if ((size_t)(heap_segment_reserved (ephemeral_heap_segment) - heap_segment_plan_allocated (ephemeral_heap_segment)) < soh_allocation_no_gc) { should_expand = TRUE; } } dprintf (2, ("will %s", (should_compact ? "compact" : "sweep"))); return should_compact; }
計劃階段在模擬壓縮和判斷後會在內部包含重定位階段(relocate_phase),壓縮階段(compact_phase)和清掃階段(sweep_phase)的處理,
接下來咱們仔細分析一下這三個階段作了什麼事情:
重定位階段的主要工做是修改對象的指針地址,例如A.Member的Member內存移動後,A中指向Member的指針地址也須要改變。
重定位階段只會修改指針地址,複製內存會交給下面的壓縮階段(compact_phase)完成。
以下圖:
圖中對象A和對象B引用了對象C,重定位後各個對象還在原來的位置,只是成員的地址(指針)變化了。
還記得以前標記階段(mark_phase)使用的GcScanRoots
等掃描函數嗎?
這些掃描函數一樣會在重定位階段使用,只是執行的不是GCHeap::Promote
而是GCHeap::Relocate
。
重定位對象會藉助計劃階段(plan_phase)構建的brick table
和plug樹來進行快速的定位,而後對指針地址移動所屬plug的reloc
位置。
gc_heap::relocate_phase
函數的代碼以下:
void gc_heap::relocate_phase (int condemned_gen_number, uint8_t* first_condemned_address) { // 生成掃描上下文 ScanContext sc; sc.thread_number = heap_number; sc.promotion = FALSE; sc.concurrent = FALSE; // 統計重定位階段的開始時間 #ifdef TIME_GC unsigned start; unsigned finish; start = GetCycleCount32(); #endif //TIME_GC // %type% category = quote (relocate); dprintf (2,("---- Relocate phase -----")); #ifdef MULTIPLE_HEAPS //join all threads to make sure they are synchronized dprintf(3, ("Joining after end of plan")); gc_t_join.join(this, gc_join_begin_relocate_phase); if (gc_t_join.joined()) #endif //MULTIPLE_HEAPS { #ifdef MULTIPLE_HEAPS //join all threads to make sure they are synchronized dprintf(3, ("Restarting for relocation")); gc_t_join.restart(); #endif //MULTIPLE_HEAPS } // 掃描根對象(各個線程中棧和寄存器中的對象) // 對掃描到的各個對象調用`GCHeap::Relocate`函數 // 注意`GCHeap::Relocate`函數不會重定位子對象,這裏只是用來重定位來源於根對象的引用 dprintf(3,("Relocating roots")); GCScan::GcScanRoots(GCHeap::Relocate, condemned_gen_number, max_generation, &sc); verify_pins_with_post_plug_info("after reloc stack"); #ifdef BACKGROUND_GC if (recursive_gc_sync::background_running_p()) { scan_background_roots (GCHeap::Relocate, heap_number, &sc); } #endif //BACKGROUND_GC // 非Full GC時,遍歷Card Table重定位小對象 // 同上,`gc_heap::relocate_address`函數不會重定位子對象,這裏只是用來重定位來源於舊代的引用 if (condemned_gen_number != max_generation) { dprintf(3,("Relocating cross generation pointers")); mark_through_cards_for_segments (&gc_heap::relocate_address, TRUE); verify_pins_with_post_plug_info("after reloc cards"); } // 非Full GC時,遍歷Card Table重定位大對象 // 同上,`gc_heap::relocate_address`函數不會重定位子對象,這裏只是用來重定位來源於舊代的引用 if (condemned_gen_number != max_generation) { dprintf(3,("Relocating cross generation pointers for large objects")); mark_through_cards_for_large_objects (&gc_heap::relocate_address, TRUE); } else { // Full GC時,若是啓用了大對象壓縮則壓縮大對象的堆 #ifdef FEATURE_LOH_COMPACTION if (loh_compacted_p) { assert (settings.condemned_generation == max_generation); relocate_in_loh_compact(); } else #endif //FEATURE_LOH_COMPACTION { relocate_in_large_objects (); } } // 重定位存活下來的對象中的引用(收集代中的對象) // 枚舉brick table對各個plug中的對象調用`relocate_obj_helper`重定位它們的成員 { dprintf(3,("Relocating survivors")); relocate_survivors (condemned_gen_number, first_condemned_address); } // 掃描在析構隊列中的對象 #ifdef FEATURE_PREMORTEM_FINALIZATION dprintf(3,("Relocating finalization data")); finalize_queue->RelocateFinalizationData (condemned_gen_number, __this); #endif // FEATURE_PREMORTEM_FINALIZATION // 掃描在GC Handle表中的對象 // MTHTS { dprintf(3,("Relocating handle table")); GCScan::GcScanHandles(GCHeap::Relocate, condemned_gen_number, max_generation, &sc); } #ifdef MULTIPLE_HEAPS //join all threads to make sure they are synchronized dprintf(3, ("Joining after end of relocation")); gc_t_join.join(this, gc_join_relocate_phase_done); #endif //MULTIPLE_HEAPS // 統計重定位階段的結束時間 #ifdef TIME_GC finish = GetCycleCount32(); reloc_time = finish - start; #endif //TIME_GC dprintf(2,( "---- End of Relocate phase ----")); }
GCHeap::Relocate
函數的代碼以下:
// ppObject是保存對象地址的地址,例如&A.Member void GCHeap::Relocate (Object** ppObject, ScanContext* sc, uint32_t flags) { UNREFERENCED_PARAMETER(sc); // 對象的地址 uint8_t* object = (uint8_t*)(Object*)(*ppObject); THREAD_NUMBER_FROM_CONTEXT; //dprintf (3, ("Relocate location %Ix\n", (size_t)ppObject)); dprintf (3, ("R: %Ix", (size_t)ppObject)); // 空指針不處理 if (object == 0) return; // 獲取對象所屬的gc_heap gc_heap* hp = gc_heap::heap_of (object); // 驗證對象是否合法,除錯用 // 若是object不必定是對象的開始地址,則不作驗證 #ifdef _DEBUG if (!(flags & GC_CALL_INTERIOR)) { // We cannot validate this object if it's in the condemned gen because it could // be one of the objects that were overwritten by an artificial gap due to a pinned plug. if (!((object >= hp->gc_low) && (object < hp->gc_high))) { ((CObjectHeader*)object)->Validate(FALSE); } } #endif //_DEBUG dprintf (3, ("Relocate %Ix\n", (size_t)object)); uint8_t* pheader; // 若是object不必定是對象的開始地址,找到對象的開始地址並重定位該開始地址,而後修改ppObject // 例如object是0x10000008,對象的開始地址是0x10000000,重定位後是0x0fff0000則*ppObject會設爲0x0fff0008 if ((flags & GC_CALL_INTERIOR) && gc_heap::settings.loh_compaction) { if (!((object >= hp->gc_low) && (object < hp->gc_high))) { return; } if (gc_heap::loh_object_p (object)) { pheader = hp->find_object (object, 0); if (pheader == 0) { return; } ptrdiff_t ref_offset = object - pheader; hp->relocate_address(&pheader THREAD_NUMBER_ARG); *ppObject = (Object*)(pheader + ref_offset); return; } } // 若是object是對象的開始地址則重定位object { pheader = object; hp->relocate_address(&pheader THREAD_NUMBER_ARG); *ppObject = (Object*)pheader; } STRESS_LOG_ROOT_RELOCATE(ppObject, object, pheader, ((!(flags & GC_CALL_INTERIOR)) ? ((Object*)object)->GetGCSafeMethodTable() : 0)); }
gc_heap::relocate_address
函數的代碼以下:
void gc_heap::relocate_address (uint8_t** pold_address THREAD_NUMBER_DCL) { // 不在本次gc回收範圍內的對象指針不須要移動 uint8_t* old_address = *pold_address; if (!((old_address >= gc_low) && (old_address < gc_high))) #ifdef MULTIPLE_HEAPS { UNREFERENCED_PARAMETER(thread); if (old_address == 0) return; gc_heap* hp = heap_of (old_address); if ((hp == this) || !((old_address >= hp->gc_low) && (old_address < hp->gc_high))) return; } #else //MULTIPLE_HEAPS return ; #endif //MULTIPLE_HEAPS // 根據對象找到對應的brick // delta translates old_address into address_gc (old_address); size_t brick = brick_of (old_address); int brick_entry = brick_table [ brick ]; uint8_t* new_address = old_address; if (! ((brick_entry == 0))) { retry: { // 若是是負數則向前繼續找 while (brick_entry < 0) { brick = (brick + brick_entry); brick_entry = brick_table [ brick ]; } uint8_t* old_loc = old_address; // 根據plug樹搜索對象所在的plug uint8_t* node = tree_search ((brick_address (brick) + brick_entry-1), old_loc); // 找到時肯定新的地址,找不到時繼續找前面的brick(有可能在上一個brick中) if ((node <= old_loc)) new_address = (old_address + node_relocation_distance (node)); else { if (node_left_p (node)) { dprintf(3,(" L: %Ix", (size_t)node)); new_address = (old_address + (node_relocation_distance (node) + node_gap_size (node))); } else { brick = brick - 1; brick_entry = brick_table [ brick ]; goto retry; } } } // 修改對象指針的地址 *pold_address = new_address; return; } // 若是對象是大對象,對象自己就是一個plug因此能夠直接取到reloc #ifdef FEATURE_LOH_COMPACTION if (loh_compacted_p #ifdef FEATURE_BASICFREEZE && !frozen_object_p((Object*)old_address) #endif // FEATURE_BASICFREEZE ) { *pold_address = old_address + loh_node_relocation_distance (old_address); } else #endif //FEATURE_LOH_COMPACTION { *pold_address = new_address; } }
gc_heap::relocate_survivors
函數的代碼以下:
這個函數用於重定位存活下來的對象中的引用
void gc_heap::relocate_survivors (int condemned_gen_number, uint8_t* first_condemned_address) { generation* condemned_gen = generation_of (condemned_gen_number); uint8_t* start_address = first_condemned_address; size_t current_brick = brick_of (start_address); heap_segment* current_heap_segment = heap_segment_rw (generation_start_segment (condemned_gen)); PREFIX_ASSUME(current_heap_segment != NULL); uint8_t* end_address = 0; // 重設mark_stack_array隊列 reset_pinned_queue_bos(); // 更新gc_heap中的oldest_pinned_plug對象 update_oldest_pinned_plug(); end_address = heap_segment_allocated (current_heap_segment); size_t end_brick = brick_of (end_address - 1); // 初始化重定位參數 relocate_args args; // 本次gc的回收範圍 args.low = gc_low; args.high = gc_high; // 當前的plug結尾是否被下一個plug覆蓋了 args.is_shortened = FALSE // last_plug或者last_plug後面的pinned plug // 處理plug尾部數據覆蓋時須要用到它 args.pinned_plug_entry = 0; // 上一個plug,用於遍歷樹時能夠從小地址到大地址遍歷(中序遍歷) args.last_plug = 0; while (1) { // 當前segment已經處理完 if (current_brick > end_brick) { // 處理最後一個plug,結尾地址是heap_segment_allocated if (args.last_plug) { { assert (!(args.is_shortened)); relocate_survivors_in_plug (args.last_plug, heap_segment_allocated (current_heap_segment), args.is_shortened, args.pinned_plug_entry); } args.last_plug = 0; } // 若是有下一個segment則處理下一個 if (heap_segment_next_rw (current_heap_segment)) { current_heap_segment = heap_segment_next_rw (current_heap_segment); current_brick = brick_of (heap_segment_mem (current_heap_segment)); end_brick = brick_of (heap_segment_allocated (current_heap_segment)-1); continue; } else { break; } } { // 若是當前brick有對應的plug樹,處理當前brick int brick_entry = brick_table [ current_brick ]; if (brick_entry >= 0) { relocate_survivors_in_brick (brick_address (current_brick) + brick_entry -1, &args); } } current_brick++; } }
gc_heap::relocate_survivors_in_brick
函數的代碼以下:
void gc_heap::relocate_survivors_in_brick (uint8_t* tree, relocate_args* args) { // 遍歷plug樹 // 會從小到大調用relocate_survivors_in_plug (中序遍歷, 藉助args->last_plug) // 例若有這樣的plug樹 // a // b c // d e // 枚舉順序是a b d e c // 調用relocate_survivors_in_plug的順序是d b e a c assert ((tree != NULL)); dprintf (3, ("tree: %Ix, args->last_plug: %Ix, left: %Ix, right: %Ix, gap(t): %Ix", tree, args->last_plug, (tree + node_left_child (tree)), (tree + node_right_child (tree)), node_gap_size (tree))); // 處理左節點 if (node_left_child (tree)) { relocate_survivors_in_brick (tree + node_left_child (tree), args); } // 處理last_plug { uint8_t* plug = tree; BOOL has_post_plug_info_p = FALSE; BOOL has_pre_plug_info_p = FALSE; // 若是這個plug是pinned plug // 獲取是否有has_pre_plug_info_p (是否覆蓋了last_plug的尾部) // 獲取是否有has_post_plug_info_p (是否被下一個plug覆蓋了尾部) if (tree == oldest_pinned_plug) { args->pinned_plug_entry = get_oldest_pinned_entry (&has_pre_plug_info_p, &has_post_plug_info_p); assert (tree == pinned_plug (args->pinned_plug_entry)); dprintf (3, ("tree is the oldest pin: %Ix", tree)); } // 處理last_plug if (args->last_plug) { size_t gap_size = node_gap_size (tree); // last_plug的結尾 = 當前plug的開始地址 - gap uint8_t* gap = (plug - gap_size); dprintf (3, ("tree: %Ix, gap: %Ix (%Ix)", tree, gap, gap_size)); assert (gap_size >= Align (min_obj_size)); uint8_t* last_plug_end = gap; // last_plug的尾部是否被覆蓋了 // args->is_shortened表明last_plug是pinned_plug,被下一個unpinned plug覆蓋了尾部 // has_pre_plug_info_p表明last_plug是unpinned plug,被下一個pinned plug覆蓋了尾部 BOOL check_last_object_p = (args->is_shortened || has_pre_plug_info_p); // 處理last_plug,結尾地址是當前plug的開始地址 - gap { relocate_survivors_in_plug (args->last_plug, last_plug_end, check_last_object_p, args->pinned_plug_entry); } } else { assert (!has_pre_plug_info_p); } // 設置last_plug args->last_plug = plug; // 設置是否被覆蓋了尾部 args->is_shortened = has_post_plug_info_p; if (has_post_plug_info_p) { dprintf (3, ("setting %Ix as shortened", plug)); } dprintf (3, ("last_plug: %Ix(shortened: %d)", plug, (args->is_shortened ? 1 : 0))); } // 處理右節點 if (node_right_child (tree)) { relocate_survivors_in_brick (tree + node_right_child (tree), args); } }
gc_heap::relocate_survivors_in_plug
函數的代碼以下:
void gc_heap::relocate_survivors_in_plug (uint8_t* plug, uint8_t* plug_end, BOOL check_last_object_p, mark* pinned_plug_entry) { //dprintf(3,("Relocating pointers in Plug [%Ix,%Ix[", (size_t)plug, (size_t)plug_end)); dprintf (3,("RP: [%Ix,%Ix[", (size_t)plug, (size_t)plug_end)); // plug的結尾被覆蓋過,須要特殊的處理 if (check_last_object_p) { relocate_shortened_survivor_helper (plug, plug_end, pinned_plug_entry); } // 通常的處理 else { relocate_survivor_helper (plug, plug_end); } }
gc_heap::relocate_survivor_helper
函數的代碼以下:
void gc_heap::relocate_survivor_helper (uint8_t* plug, uint8_t* plug_end) { // 枚舉plug中的對象,分別調用relocate_obj_helper函數 uint8_t* x = plug; while (x < plug_end) { size_t s = size (x); uint8_t* next_obj = x + Align (s); Prefetch (next_obj); relocate_obj_helper (x, s); assert (s > 0); x = next_obj; } }
gc_heap::relocate_obj_helper
函數的代碼以下:
inline void gc_heap::relocate_obj_helper (uint8_t* x, size_t s) { THREAD_FROM_HEAP; // 判斷對象中是否包含了引用 if (contain_pointers (x)) { dprintf (3, ("$%Ix$", (size_t)x)); // 重定位這個對象的全部成員 // 注意這裏不會包含對象自身(nostart) go_through_object_nostart (method_table(x), x, s, pval, { uint8_t* child = *pval; reloc_survivor_helper (pval); if (child) { dprintf (3, ("%Ix->%Ix->%Ix", (uint8_t*)pval, child, *pval)); } }); } check_class_object_demotion (x); }
gc_heap::reloc_survivor_helper
函數的代碼以下:
inline void gc_heap::reloc_survivor_helper (uint8_t** pval) { // 執行重定位,relocate_address函數上面有解釋 THREAD_FROM_HEAP; relocate_address (pval THREAD_NUMBER_ARG); // 若是對象在降代範圍中,須要設置來源位置在Card Table中的標記 check_demotion_helper (pval, (uint8_t*)pval); }
gc_heap::relocate_shortened_survivor_helper
函數的代碼以下:
void gc_heap::relocate_shortened_survivor_helper (uint8_t* plug, uint8_t* plug_end, mark* pinned_plug_entry) { uint8_t* x = plug; // 若是p_plug == plug表示當前plug是pinned plug,結尾被下一個plug覆蓋 // 若是p_plug != plug表示當前plug是unpinned plug,結尾被p_plug覆蓋 uint8_t* p_plug = pinned_plug (pinned_plug_entry); BOOL is_pinned = (plug == p_plug); BOOL check_short_obj_p = (is_pinned ? pinned_plug_entry->post_short_p() : pinned_plug_entry->pre_short_p()); // 由於這個plug的結尾被覆蓋了,下一個plug的gap是特殊gap,這裏要加回去大小 plug_end += sizeof (gap_reloc_pair); //dprintf (3, ("%s %Ix is shortened, and last object %s overwritten", (is_pinned ? "PP" : "NP"), plug, (check_short_obj_p ? "is" : "is not"))); dprintf (3, ("%s %Ix-%Ix short, LO: %s OW", (is_pinned ? "PP" : "NP"), plug, plug_end, (check_short_obj_p ? "is" : "is not"))); verify_pins_with_post_plug_info("begin reloc short surv"); // 枚舉plug中的對象 while (x < plug_end) { // plug的最後一個對象被徹底覆蓋了,須要作特殊處理 if (check_short_obj_p && ((plug_end - x) < min_pre_pin_obj_size)) { dprintf (3, ("last obj %Ix is short", x)); // 當前plug是pinned plug,結尾被下一個unpinned plug覆蓋了 // 根據最後一個對象的成員bitmap重定位 if (is_pinned) { #ifdef COLLECTIBLE_CLASS if (pinned_plug_entry->post_short_collectible_p()) unconditional_set_card_collectible (x); #endif //COLLECTIBLE_CLASS // Relocate the saved references based on bits set. // 成員應該存在的地址(被覆蓋的數據中),設置Card Table會使用這個地址 uint8_t** saved_plug_info_start = (uint8_t**)(pinned_plug_entry->get_post_plug_info_start()); // 成員真實存在的地址(備份數據中) uint8_t** saved_info_to_relocate = (uint8_t**)(pinned_plug_entry->get_post_plug_reloc_info()); // 枚舉成員的bitmap for (size_t i = 0; i < pinned_plug_entry->get_max_short_bits(); i++) { // 若是成員存在則重定位該成員 if (pinned_plug_entry->post_short_bit_p (i)) { reloc_ref_in_shortened_obj ((saved_plug_info_start + i), (saved_info_to_relocate + i)); } } } // 當前plug是unpinned plug,結尾被下一個pinned plug覆蓋了 // 處理和上面同樣 else { #ifdef COLLECTIBLE_CLASS if (pinned_plug_entry->pre_short_collectible_p()) unconditional_set_card_collectible (x); #endif //COLLECTIBLE_CLASS relocate_pre_plug_info (pinned_plug_entry); // Relocate the saved references based on bits set. uint8_t** saved_plug_info_start = (uint8_t**)(p_plug - sizeof (plug_and_gap)); uint8_t** saved_info_to_relocate = (uint8_t**)(pinned_plug_entry->get_pre_plug_reloc_info()); for (size_t i = 0; i < pinned_plug_entry->get_max_short_bits(); i++) { if (pinned_plug_entry->pre_short_bit_p (i)) { reloc_ref_in_shortened_obj ((saved_plug_info_start + i), (saved_info_to_relocate + i)); } } } // 處理完最後一個對象,能夠跳出了 break; } size_t s = size (x); uint8_t* next_obj = x + Align (s); Prefetch (next_obj); // 最後一個對象被覆蓋了,可是隻是覆蓋了後半部分,不是所有被覆蓋 if (next_obj >= plug_end) { dprintf (3, ("object %Ix is at the end of the plug %Ix->%Ix", next_obj, plug, plug_end)); verify_pins_with_post_plug_info("before reloc short obj"); relocate_shortened_obj_helper (x, s, (x + Align (s) - sizeof (plug_and_gap)), pinned_plug_entry, is_pinned); } // 對象未被覆蓋,調用通常的處理 else { relocate_obj_helper (x, s); } assert (s > 0); x = next_obj; } verify_pins_with_post_plug_info("end reloc short surv"); }
gc_heap::reloc_ref_in_shortened_obj
函數的代碼以下:
inline void gc_heap::reloc_ref_in_shortened_obj (uint8_t** address_to_set_card, uint8_t** address_to_reloc) { THREAD_FROM_HEAP; // 重定位對象 // 這裏的address_to_reloc會在備份數據中 uint8_t* old_val = (address_to_reloc ? *address_to_reloc : 0); relocate_address (address_to_reloc THREAD_NUMBER_ARG); if (address_to_reloc) { dprintf (3, ("SR %Ix: %Ix->%Ix", (uint8_t*)address_to_reloc, old_val, *address_to_reloc)); } // 若是對象在降代範圍中,設置Card Table // 這裏的address_to_set_card會在被覆蓋的數據中 //check_demotion_helper (current_saved_info_to_relocate, (uint8_t*)pval); uint8_t* relocated_addr = *address_to_reloc; if ((relocated_addr < demotion_high) && (relocated_addr >= demotion_low)) { dprintf (3, ("set card for location %Ix(%Ix)", (size_t)address_to_set_card, card_of((uint8_t*)address_to_set_card))); set_card (card_of ((uint8_t*)address_to_set_card)); } #ifdef MULTIPLE_HEAPS // 不在當前heap時試着找到對象所在的heap而且用該heap處理 else if (settings.demotion) { gc_heap* hp = heap_of (relocated_addr); if ((relocated_addr < hp->demotion_high) && (relocated_addr >= hp->demotion_low)) { dprintf (3, ("%Ix on h%d, set card for location %Ix(%Ix)", relocated_addr, hp->heap_number, (size_t)address_to_set_card, card_of((uint8_t*)address_to_set_card))); set_card (card_of ((uint8_t*)address_to_set_card)); } } #endif //MULTIPLE_HEAPS }
gc_heap::relocate_shortened_obj_helper
函數的代碼以下:
inline void gc_heap::relocate_shortened_obj_helper (uint8_t* x, size_t s, uint8_t* end, mark* pinned_plug_entry, BOOL is_pinned) { THREAD_FROM_HEAP; uint8_t* plug = pinned_plug (pinned_plug_entry); // 若是當前plug是unpinned plug, 表明鄰接的pinned plug中保存的pre_plug_info_reloc_start可能已經被移動了 // 這裏須要重定位pinned plug中保存的pre_plug_info_reloc_start (unpinned plug被覆蓋的內容的開始地址) if (!is_pinned) { //// Temporary - we just wanna make sure we are doing things right when padding is needed. //if ((x + s) < plug) //{ // dprintf (3, ("obj %Ix needed padding: end %Ix is %d bytes from pinned obj %Ix", // x, (x + s), (plug- (x + s)), plug)); // GCToOSInterface::DebugBreak(); //} relocate_pre_plug_info (pinned_plug_entry); } verify_pins_with_post_plug_info("after relocate_pre_plug_info"); uint8_t* saved_plug_info_start = 0; uint8_t** saved_info_to_relocate = 0; // saved_plug_info_start等於被覆蓋的地址的開始 // saved_info_to_relocate等於原始內容的開始 if (is_pinned) { saved_plug_info_start = (uint8_t*)(pinned_plug_entry->get_post_plug_info_start()); saved_info_to_relocate = (uint8_t**)(pinned_plug_entry->get_post_plug_reloc_info()); } else { saved_plug_info_start = (plug - sizeof (plug_and_gap)); saved_info_to_relocate = (uint8_t**)(pinned_plug_entry->get_pre_plug_reloc_info()); } uint8_t** current_saved_info_to_relocate = 0; uint8_t* child = 0; dprintf (3, ("x: %Ix, pp: %Ix, end: %Ix", x, plug, end)); // 判斷對象中是否包含了引用 if (contain_pointers (x)) { dprintf (3,("$%Ix$", (size_t)x)); // 重定位這個對象的全部成員 // 注意這裏不會包含對象自身(nostart) go_through_object_nostart (method_table(x), x, s, pval, { dprintf (3, ("obj %Ix, member: %Ix->%Ix", x, (uint8_t*)pval, *pval)); // 成員所在的部分被覆蓋了,調用reloc_ref_in_shortened_obj重定位 // pval = 成員應該存在的地址(被覆蓋的數據中),設置Card Table會使用這個地址 // current_saved_info_to_relocate = 成員真實存在的地址(備份數據中) if ((uint8_t*)pval >= end) { current_saved_info_to_relocate = saved_info_to_relocate + ((uint8_t*)pval - saved_plug_info_start) / sizeof (uint8_t**); child = *current_saved_info_to_relocate; reloc_ref_in_shortened_obj (pval, current_saved_info_to_relocate); dprintf (3, ("last part: R-%Ix(saved: %Ix)->%Ix ->%Ix", (uint8_t*)pval, current_saved_info_to_relocate, child, *current_saved_info_to_relocate)); } // 成員所在的部分未被覆蓋,調用通常的處理 else { reloc_survivor_helper (pval); } }); } check_class_object_demotion (x); }
重定位階段(relocate_phase)只是修改了引用對象的地址,對象還在原來的位置,接下來進入壓縮階段(compact_phase):
壓縮階段負責把對象複製到以前模擬壓縮到的地址上,簡單點來說就是用memcpy
複製這些對象到新的地址。
壓縮階段會使用以前構建的brick table和plug樹快速的枚舉對象。
gc_heap::compact_phase
函數的代碼以下:
這個函數的代碼是否是有點眼熟?它的流程和上面的relocate_survivors
很像,都是枚舉brick table而後中序枚舉plug樹
void gc_heap::compact_phase (int condemned_gen_number, uint8_t* first_condemned_address, BOOL clear_cards) { // %type% category = quote (compact); // 統計壓縮階段的開始時間 #ifdef TIME_GC unsigned start; unsigned finish; start = GetCycleCount32(); #endif //TIME_GC generation* condemned_gen = generation_of (condemned_gen_number); uint8_t* start_address = first_condemned_address; size_t current_brick = brick_of (start_address); heap_segment* current_heap_segment = heap_segment_rw (generation_start_segment (condemned_gen)); PREFIX_ASSUME(current_heap_segment != NULL); // 重設mark_stack_array隊列 reset_pinned_queue_bos(); // 更新gc_heap中的oldest_pinned_plug對象 update_oldest_pinned_plug(); // 若是should_expand的時候重用了之前的segment做爲ephemeral heap segment,則須要從新計算generation_allocation_size // reused_seg會影響壓縮參數中的check_gennum_p BOOL reused_seg = expand_reused_seg_p(); if (reused_seg) { for (int i = 1; i <= max_generation; i++) { generation_allocation_size (generation_of (i)) = 0; } } uint8_t* end_address = heap_segment_allocated (current_heap_segment); size_t end_brick = brick_of (end_address-1); // 初始化壓縮參數 compact_args args; // 上一個plug,用於遍歷樹時能夠從小地址到大地址遍歷(中序遍歷) args.last_plug = 0; // 當前brick的最後一個plug,更新brick table時使用 args.before_last_plug = 0; // 最後設置的brick,用於複製plug後更新brick table args.current_compacted_brick = ~((size_t)1); // 當前的plug結尾是否被下一個plug覆蓋了 args.is_shortened = FALSE; // last_plug或者last_plug後面的pinned plug // 處理plug尾部數據覆蓋時須要用到它 args.pinned_plug_entry = 0; // 是否須要在複製對象時複製相應的Card Table範圍 args.copy_cards_p = (condemned_gen_number >= 1) || !clear_cards; // 從新計算generation_allocation_size時使用的參數 args.check_gennum_p = reused_seg; if (args.check_gennum_p) { args.src_gennum = ((current_heap_segment == ephemeral_heap_segment) ? -1 : 2); } dprintf (2,("---- Compact Phase: %Ix(%Ix)----", first_condemned_address, brick_of (first_condemned_address))); #ifdef MULTIPLE_HEAPS //restart if (gc_t_join.joined()) { #endif //MULTIPLE_HEAPS #ifdef MULTIPLE_HEAPS dprintf(3, ("Restarting for compaction")); gc_t_join.restart(); } #endif //MULTIPLE_HEAPS // 再次重設mark_stack_array隊列 reset_pinned_queue_bos(); // 判斷是否須要壓縮大對象的堆 #ifdef FEATURE_LOH_COMPACTION if (loh_compacted_p) { compact_loh(); } #endif //FEATURE_LOH_COMPACTION // 循環brick table if ((start_address < end_address) || (condemned_gen_number == max_generation)) { while (1) { // 當前segment已經處理完 if (current_brick > end_brick) { // 處理最後一個plug,大小是heap_segment_allocated - last_plug if (args.last_plug != 0) { dprintf (3, ("compacting last plug: %Ix", args.last_plug)) compact_plug (args.last_plug, (heap_segment_allocated (current_heap_segment) - args.last_plug), args.is_shortened, &args); } // 若是有下一個segment則處理下一個 if (heap_segment_next_rw (current_heap_segment)) { current_heap_segment = heap_segment_next_rw (current_heap_segment); current_brick = brick_of (heap_segment_mem (current_heap_segment)); end_brick = brick_of (heap_segment_allocated (current_heap_segment)-1); args.last_plug = 0; // 更新src_gennum (若是segment是ephemeral_heap_segment則須要進一步判斷) if (args.check_gennum_p) { args.src_gennum = ((current_heap_segment == ephemeral_heap_segment) ? -1 : 2); } continue; } // 設置最後一個brick的偏移值, 給compact_plug善後 else { if (args.before_last_plug !=0) { dprintf (3, ("Fixing last brick %Ix to point to plug %Ix", args.current_compacted_brick, (size_t)args.before_last_plug)); assert (args.current_compacted_brick != ~1u); set_brick (args.current_compacted_brick, args.before_last_plug - brick_address (args.current_compacted_brick)); } break; } } { // 若是當前brick有對應的plug樹,處理當前brick int brick_entry = brick_table [ current_brick ]; dprintf (3, ("B: %Ix(%Ix)->%Ix", current_brick, (size_t)brick_entry, (brick_address (current_brick) + brick_entry - 1))); if (brick_entry >= 0) { compact_in_brick ((brick_address (current_brick) + brick_entry -1), &args); } } current_brick++; } } // 複製已完畢 // 恢復備份的數據到被覆蓋的部分 recover_saved_pinned_info(); // 統計壓縮階段的結束時間 #ifdef TIME_GC finish = GetCycleCount32(); compact_time = finish - start; #endif //TIME_GC concurrent_print_time_delta ("compact end"); dprintf(2,("---- End of Compact phase ----")); }
gc_heap::compact_in_brick
函數的代碼以下:
這個函數和上面的relocate_survivors_in_brick
函數很像
void gc_heap::compact_in_brick (uint8_t* tree, compact_args* args) { assert (tree != NULL); int left_node = node_left_child (tree); int right_node = node_right_child (tree); // 須要移動的偏移值,前面計劃階段模擬壓縮時設置的reloc ptrdiff_t relocation = node_relocation_distance (tree); args->print(); // 處理左節點 if (left_node) { dprintf (3, ("B: L: %d->%Ix", left_node, (tree + left_node))); compact_in_brick ((tree + left_node), args); } uint8_t* plug = tree; BOOL has_pre_plug_info_p = FALSE; BOOL has_post_plug_info_p = FALSE; // 若是這個plug是pinned plug // 獲取是否有has_pre_plug_info_p (是否覆蓋了last_plug的尾部) // 獲取是否有has_post_plug_info_p (是否被下一個plug覆蓋了尾部) if (tree == oldest_pinned_plug) { args->pinned_plug_entry = get_oldest_pinned_entry (&has_pre_plug_info_p, &has_post_plug_info_p); assert (tree == pinned_plug (args->pinned_plug_entry)); } // 處理last_plug if (args->last_plug != 0) { size_t gap_size = node_gap_size (tree); // last_plug的結尾 = 當前plug的開始地址 - gap uint8_t* gap = (plug - gap_size); uint8_t* last_plug_end = gap; // last_plug的大小 = last_plug的結尾 - last_plug的開始 size_t last_plug_size = (last_plug_end - args->last_plug); dprintf (3, ("tree: %Ix, last_plug: %Ix, gap: %Ix(%Ix), last_plug_end: %Ix, size: %Ix", tree, args->last_plug, gap, gap_size, last_plug_end, last_plug_size)); // last_plug的尾部是否被覆蓋了 // args->is_shortened表明last_plug是pinned_plug,被下一個unpinned plug覆蓋了尾部 // has_pre_plug_info_p表明last_plug是unpinned plug,被下一個pinned plug覆蓋了尾部 BOOL check_last_object_p = (args->is_shortened || has_pre_plug_info_p); if (!check_last_object_p) { assert (last_plug_size >= Align (min_obj_size)); } // 處理last_plug compact_plug (args->last_plug, last_plug_size, check_last_object_p, args); } else { // 第一個plug不可能覆蓋前面的plug的結尾 assert (!has_pre_plug_info_p); } dprintf (3, ("set args last plug to plug: %Ix, reloc: %Ix", plug, relocation)); // 設置last_plug args->last_plug = plug; // 設置last_plugd移動偏移值 args->last_plug_relocation = relocation; // 設置是否被覆蓋了尾部 args->is_shortened = has_post_plug_info_p; // 處理右節點 if (right_node) { dprintf (3, ("B: R: %d->%Ix", right_node, (tree + right_node))); compact_in_brick ((tree + right_node), args); } }
gc_heap::compact_plug
函數的代碼以下:
void gc_heap::compact_plug (uint8_t* plug, size_t size, BOOL check_last_object_p, compact_args* args) { args->print(); // 複製到的地址,plug + reloc uint8_t* reloc_plug = plug + args->last_plug_relocation; // 若是plug的結尾被覆蓋過 if (check_last_object_p) { // 添加特殊gap的大小 size += sizeof (gap_reloc_pair); mark* entry = args->pinned_plug_entry; // 在複製內存前把被覆蓋的內容和原始內容交換一下 // 複製內存後須要交換回去 if (args->is_shortened) { // 當前plug是pinned plug,被下一個unpinned plug覆蓋 assert (entry->has_post_plug_info()); entry->swap_post_plug_and_saved(); } else { // 當前plug是unpinned plug,被下一個pinned plug覆蓋 assert (entry->has_pre_plug_info()); entry->swap_pre_plug_and_saved(); } } // 複製以前的brick中的偏移值 int old_brick_entry = brick_table [brick_of (plug)]; assert (node_relocation_distance (plug) == args->last_plug_relocation); // 處理對齊和pad #ifdef FEATURE_STRUCTALIGN ptrdiff_t alignpad = node_alignpad(plug); if (alignpad) { make_unused_array (reloc_plug - alignpad, alignpad); if (brick_of (reloc_plug - alignpad) != brick_of (reloc_plug)) { // The alignment padding is straddling one or more bricks; // it has to be the last "object" of its first brick. fix_brick_to_highest (reloc_plug - alignpad, reloc_plug); } } #else // FEATURE_STRUCTALIGN size_t unused_arr_size = 0; BOOL already_padded_p = FALSE; #ifdef SHORT_PLUGS if (is_plug_padded (plug)) { already_padded_p = TRUE; clear_plug_padded (plug); unused_arr_size = Align (min_obj_size); } #endif //SHORT_PLUGS if (node_realigned (plug)) { unused_arr_size += switch_alignment_size (already_padded_p); } if (unused_arr_size != 0) { make_unused_array (reloc_plug - unused_arr_size, unused_arr_size); if (brick_of (reloc_plug - unused_arr_size) != brick_of (reloc_plug)) { dprintf (3, ("fix B for padding: %Id: %Ix->%Ix", unused_arr_size, (reloc_plug - unused_arr_size), reloc_plug)); // The alignment padding is straddling one or more bricks; // it has to be the last "object" of its first brick. fix_brick_to_highest (reloc_plug - unused_arr_size, reloc_plug); } } #endif // FEATURE_STRUCTALIGN #ifdef SHORT_PLUGS if (is_plug_padded (plug)) { make_unused_array (reloc_plug - Align (min_obj_size), Align (min_obj_size)); if (brick_of (reloc_plug - Align (min_obj_size)) != brick_of (reloc_plug)) { // The alignment padding is straddling one or more bricks; // it has to be the last "object" of its first brick. fix_brick_to_highest (reloc_plug - Align (min_obj_size), reloc_plug); } } #endif //SHORT_PLUGS // 複製plug中的全部內容和對應的Card Table中的範圍(若是copy_cards_p成立) gcmemcopy (reloc_plug, plug, size, args->copy_cards_p); // 從新統計generation_allocation_size if (args->check_gennum_p) { int src_gennum = args->src_gennum; if (src_gennum == -1) { src_gennum = object_gennum (plug); } int dest_gennum = object_gennum_plan (reloc_plug); if (src_gennum < dest_gennum) { generation_allocation_size (generation_of (dest_gennum)) += size; } } // 更新brick table // brick table中會保存brick的最後一個plug的偏移值,跨越多個brick的時候後面的brick會是-1 size_t current_reloc_brick = args->current_compacted_brick; // 若是已經到了下一個brick // 設置上一個brick的值 = 上一個brick中最後的plug的偏移值, 或者-1 if (brick_of (reloc_plug) != current_reloc_brick) { dprintf (3, ("last reloc B: %Ix, current reloc B: %Ix", current_reloc_brick, brick_of (reloc_plug))); if (args->before_last_plug) { dprintf (3,(" fixing last brick %Ix to point to last plug %Ix(%Ix)", current_reloc_brick, args->before_last_plug, (args->before_last_plug - brick_address (current_reloc_brick)))); { set_brick (current_reloc_brick, args->before_last_plug - brick_address (current_reloc_brick)); } } current_reloc_brick = brick_of (reloc_plug); } // 若是跨越了多個brick size_t end_brick = brick_of (reloc_plug + size-1); if (end_brick != current_reloc_brick) { // The plug is straddling one or more bricks // It has to be the last plug of its first brick dprintf (3,("plug spanning multiple bricks, fixing first brick %Ix to %Ix(%Ix)", current_reloc_brick, (size_t)reloc_plug, (reloc_plug - brick_address (current_reloc_brick)))); // 設置第一個brick中的偏移值 { set_brick (current_reloc_brick, reloc_plug - brick_address (current_reloc_brick)); } // 把後面的brick設爲-1,除了end_brick // update all intervening brick size_t brick = current_reloc_brick + 1; dprintf (3,("setting intervening bricks %Ix->%Ix to -1", brick, (end_brick - 1))); while (brick < end_brick) { set_brick (brick, -1); brick++; } // 若是end_brick中無其餘plug,end_brick也會被設爲-1 // brick_address (end_brick) - 1 - brick_address (end_brick) = -1 // code last brick offset as a plug address args->before_last_plug = brick_address (end_brick) -1; current_reloc_brick = end_brick; dprintf (3, ("setting before last to %Ix, last brick to %Ix", args->before_last_plug, current_reloc_brick)); } // 若是隻在一個brick中 else { // 記錄當前brick中的最後一個plug dprintf (3, ("still in the same brick: %Ix", end_brick)); args->before_last_plug = reloc_plug; } // 更新最後設置的brick args->current_compacted_brick = current_reloc_brick; // 複製完畢之後把被覆蓋的內容和原始內容交換回去 // 注意若是plug移動的距離比覆蓋的大小要少,這裏會把複製後的內容給破壞掉 // 後面還須要使用recover_saved_pinned_info還原 if (check_last_object_p) { mark* entry = args->pinned_plug_entry; if (args->is_shortened) { entry->swap_post_plug_and_saved(); } else { entry->swap_pre_plug_and_saved(); } } }
gc_heap::gcmemcopy
函數的代碼以下:
// POPO TODO: We should actually just recover the artifically made gaps here..because when we copy // we always copy the earlier plugs first which means we won't need the gap sizes anymore. This way // we won't need to individually recover each overwritten part of plugs. inline void gc_heap::gcmemcopy (uint8_t* dest, uint8_t* src, size_t len, BOOL copy_cards_p) { // 若是地址同樣能夠跳過 if (dest != src) { #ifdef BACKGROUND_GC if (current_c_gc_state == c_gc_state_marking) { //TODO: should look to see whether we should consider changing this // to copy a consecutive region of the mark array instead. copy_mark_bits_for_addresses (dest, src, len); } #endif //BACKGROUND_GC // 複製plug中的全部對象到新的地址上 // memcopy作的東西和memcpy同樣,微軟本身寫的一個函數而已 //dprintf(3,(" Memcopy [%Ix->%Ix, %Ix->%Ix[", (size_t)src, (size_t)dest, (size_t)src+len, (size_t)dest+len)); dprintf(3,(" mc: [%Ix->%Ix, %Ix->%Ix[", (size_t)src, (size_t)dest, (size_t)src+len, (size_t)dest+len)); memcopy (dest - plug_skew, src - plug_skew, (int)len); #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP if (SoftwareWriteWatch::IsEnabledForGCHeap()) { // The ranges [src - plug_kew .. src[ and [src + len - plug_skew .. src + len[ are ObjHeaders, which don't have GC // references, and are not relevant for write watch. The latter range actually corresponds to the ObjHeader for the // object at (src + len), so it can be ignored anyway. SoftwareWriteWatch::SetDirtyRegion(dest, len - plug_skew); } #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP // 複製對應的Card Table範圍 // copy_cards_p成立的時候複製src ~ src+len到dest // copy_cards_p不成立的時候清除dest ~ dest+len copy_cards_range (dest, src, len, copy_cards_p); } }
gc_heap::compact_loh
函數的代碼以下:
void gc_heap::compact_loh() { assert (should_compact_loh()); generation* gen = large_object_generation; heap_segment* start_seg = heap_segment_rw (generation_start_segment (gen)); PREFIX_ASSUME(start_seg != NULL); heap_segment* seg = start_seg; heap_segment* prev_seg = 0; uint8_t* o = generation_allocation_start (gen); //Skip the generation gap object o = o + AlignQword (size (o)); // We don't need to ever realloc gen3 start so don't touch it. uint8_t* free_space_start = o; uint8_t* free_space_end = o; generation_allocator (gen)->clear(); generation_free_list_space (gen) = 0; generation_free_obj_space (gen) = 0; loh_pinned_queue_bos = 0; // 枚舉大對象的堆 while (1) { // 當前segment處理完畢,處理下一個 if (o >= heap_segment_allocated (seg)) { heap_segment* next_seg = heap_segment_next (seg); // 若是當前segment爲空,表示能夠刪掉這個segment // 修改segment鏈表,把空的segment放到後面 if ((heap_segment_plan_allocated (seg) == heap_segment_mem (seg)) && (seg != start_seg) && !heap_segment_read_only_p (seg)) { dprintf (3, ("Preparing empty large segment %Ix", (size_t)seg)); assert (prev_seg); heap_segment_next (prev_seg) = next_seg; heap_segment_next (seg) = freeable_large_heap_segment; freeable_large_heap_segment = seg; } else { // 更新heap_segment_allocated // 釋放(decommit)未使用的內存空間 if (!heap_segment_read_only_p (seg)) { // We grew the segment to accommondate allocations. if (heap_segment_plan_allocated (seg) > heap_segment_allocated (seg)) { if ((heap_segment_plan_allocated (seg) - plug_skew) > heap_segment_used (seg)) { heap_segment_used (seg) = heap_segment_plan_allocated (seg) - plug_skew; } } heap_segment_allocated (seg) = heap_segment_plan_allocated (seg); dprintf (3, ("Trimming seg to %Ix[", heap_segment_allocated (seg))); decommit_heap_segment_pages (seg, 0); dprintf (1236, ("CLOH: seg: %Ix, alloc: %Ix, used: %Ix, committed: %Ix", seg, heap_segment_allocated (seg), heap_segment_used (seg), heap_segment_committed (seg))); //heap_segment_used (seg) = heap_segment_allocated (seg) - plug_skew; dprintf (1236, ("CLOH: used is set to %Ix", heap_segment_used (seg))); } prev_seg = seg; } // 處理下一個segment,不存在時跳出 seg = next_seg; if (seg == 0) break; else { o = heap_segment_mem (seg); } } // 若是對象已標記 if (marked (o)) { free_space_end = o; size_t size = AlignQword (size (o)); size_t loh_pad; uint8_t* reloc = o; // 清除標記 clear_marked (o); // 若是對象是固定的 if (pinned (o)) { // We are relying on the fact the pinned objects are always looked at in the same order // in plan phase and in compact phase. mark* m = loh_pinned_plug_of (loh_deque_pinned_plug()); uint8_t* plug = pinned_plug (m); assert (plug == o); loh_pad = pinned_len (m); // 清除固定標記 clear_pinned (o); } else { loh_pad = AlignQword (loh_padding_obj_size); // 複製對象內存 reloc += loh_node_relocation_distance (o); gcmemcopy (reloc, o, size, TRUE); } // 添加loh_pad到free list thread_gap ((reloc - loh_pad), loh_pad, gen); // 處理下一個對象 o = o + size; free_space_start = o; if (o < heap_segment_allocated (seg)) { assert (!marked (o)); } } else { // 跳過未標記對象 while (o < heap_segment_allocated (seg) && !marked (o)) { o = o + AlignQword (size (o)); } } } assert (loh_pinned_plug_que_empty_p()); dprintf (1235, ("after GC LOH size: %Id, free list: %Id, free obj: %Id\n\n", generation_size (max_generation + 1), generation_free_list_space (gen), generation_free_obj_space (gen))); }
gc_heap::recover_saved_pinned_info
函數的代碼以下:
void gc_heap::recover_saved_pinned_info() { // 重設mark_stack_array隊列 reset_pinned_queue_bos(); // 恢復各個pinned plug被覆蓋或者覆蓋的數據 while (!(pinned_plug_que_empty_p())) { mark* oldest_entry = oldest_pin(); oldest_entry->recover_plug_info(); #ifdef GC_CONFIG_DRIVEN if (oldest_entry->has_pre_plug_info() && oldest_entry->has_post_plug_info()) record_interesting_data_point (idp_pre_and_post_pin); else if (oldest_entry->has_pre_plug_info()) record_interesting_data_point (idp_pre_pin); else if (oldest_entry->has_post_plug_info()) record_interesting_data_point (idp_post_pin); #endif //GC_CONFIG_DRIVEN deque_pinned_plug(); } }
mark::recover_plug_info
函數的代碼以下:
函數前面的註釋講的是以前複製plug的時候已經包含了被覆蓋的內容(swap_pre_plug_and_saved
),
可是若是移動的位置小於3個指針的大小(註釋中的< 3
應該是>= 3
)則複製完之後有可能再次被swap_pre_plug_and_saved
破壞掉。
// We should think about whether it's really necessary to have to copy back the pre plug // info since it was already copied during compacting plugs. But if a plug doesn't move // by < 3 ptr size, it means we'd have to recover pre plug info. void recover_plug_info() { // 若是這個pinned plug覆蓋了前一個unpinned plug的結尾,把備份的數據恢復回去 if (saved_pre_p) { // 若是已經壓縮過,須要複製到重定位後的saved_pre_plug_info_reloc_start // 而且使用saved_pre_plug_reloc備份(這個備份裏面的成員也通過了重定位) if (gc_heap::settings.compaction) { dprintf (3, ("%Ix: REC Pre: %Ix-%Ix", first, &saved_pre_plug_reloc, saved_pre_plug_info_reloc_start)); memcpy (saved_pre_plug_info_reloc_start, &saved_pre_plug_reloc, sizeof (saved_pre_plug_reloc)); } // 若是未壓縮過,能夠複製到這個pinned plug的前面 // 而且使用saved_pre_plug備份 else { dprintf (3, ("%Ix: REC Pre: %Ix-%Ix", first, &saved_pre_plug, (first - sizeof (plug_and_gap)))); memcpy ((first - sizeof (plug_and_gap)), &saved_pre_plug, sizeof (saved_pre_plug)); } } // 若是這個pinned plug被下一個unpinned plug覆蓋告終尾,把備份的數據恢復回去 if (saved_post_p) { // 由於pinned plug不會移動 // 這裏的saved_post_plug_info_start不會改變 // 使用saved_post_plug_reloc備份(這個備份裏面的成員也通過了重定位) if (gc_heap::settings.compaction) { dprintf (3, ("%Ix: REC Post: %Ix-%Ix", first, &saved_post_plug_reloc, saved_post_plug_info_start)); memcpy (saved_post_plug_info_start, &saved_post_plug_reloc, sizeof (saved_post_plug_reloc)); } // 使用saved_pre_plug備份 else { dprintf (3, ("%Ix: REC Post: %Ix-%Ix", first, &saved_post_plug, saved_post_plug_info_start)); memcpy (saved_post_plug_info_start, &saved_post_plug, sizeof (saved_post_plug)); } } }
壓縮階段結束之後還須要作一些收尾工做,請從上面plan_phase
中的fix_generation_bounds (condemned_gen_number, consing_gen);
繼續看。
若是計劃階段不選擇壓縮,就會進入清掃階段:
清掃階段負責把plug與plug之間的空間變爲free object
而後加到對應代的free list
中,而且負責修改代邊界。
加到free list
中的區域會在後面供分配新的上下文使用。
清掃階段的主要工做在函數make_free_lists
中完成,名稱叫sweep_phase
的函數目前不存在。
掃描plug時會使用計劃階段構建好的plug信息和brick table
,但模擬壓縮的偏移值reloc
和計劃代邊界plan_allocation_start
不會被使用。
gc_heap::make_free_lists
函數的代碼以下:
void gc_heap::make_free_lists (int condemned_gen_number) { // 統計清掃階段的開始時間 #ifdef TIME_GC unsigned start; unsigned finish; start = GetCycleCount32(); #endif //TIME_GC //Promotion has to happen in sweep case. assert (settings.promotion); // 從收集代的第一個segment開始處理 generation* condemned_gen = generation_of (condemned_gen_number); uint8_t* start_address = generation_allocation_start (condemned_gen); size_t current_brick = brick_of (start_address); heap_segment* current_heap_segment = heap_segment_rw (generation_start_segment (condemned_gen)); PREFIX_ASSUME(current_heap_segment != NULL); uint8_t* end_address = heap_segment_allocated (current_heap_segment); size_t end_brick = brick_of (end_address-1); // 清掃階段使用的參數 make_free_args args; // 當前生成的free object應該歸到的代序號 // 更新代邊界的時候也會使用 args.free_list_gen_number = min (max_generation, 1 + condemned_gen_number); // 超過這個值就須要更新free_list_gen_number和free_list_gen // 在清掃階段settings.promotion == true時 // generation_limit遇到gen 0或者gen 1的時候返回heap_segment_reserved (ephemeral_heap_segment),則原代0的對象歸到代1 // generation_limit遇到gen 2的時候返回generation_allocation_start (generation_of ((gen_number - 2))),則原代1的對象歸到代2 // MAX_PTR只是用來檢測第一次使用的,後面會更新 args.current_gen_limit = (((condemned_gen_number == max_generation)) ? MAX_PTR : (generation_limit (args.free_list_gen_number))); // 當前生成的free object應該歸到的代 args.free_list_gen = generation_of (args.free_list_gen_number); // 當前brick中地址最大的plug,用於更新brick表 args.highest_plug = 0; // 開始遍歷brick if ((start_address < end_address) || (condemned_gen_number == max_generation)) { while (1) { // 當前segment處理完畢 if ((current_brick > end_brick)) { // 若是第一個segment無存活的對象,則重設它的heap_segment_allocated // 而且設置generation_allocation_start (gen)等於這個空segment的開始地址 if (args.current_gen_limit == MAX_PTR) { //We had an empty segment //need to allocate the generation start generation* gen = generation_of (max_generation); heap_segment* start_seg = heap_segment_rw (generation_start_segment (gen)); PREFIX_ASSUME(start_seg != NULL); uint8_t* gap = heap_segment_mem (start_seg); generation_allocation_start (gen) = gap; heap_segment_allocated (start_seg) = gap + Align (min_obj_size); // 確保代最少有一個對象 make_unused_array (gap, Align (min_obj_size)); // 更新代邊界 reset_allocation_pointers (gen, gap); dprintf (3, ("Start segment empty, fixing generation start of %d to: %Ix", max_generation, (size_t)gap)); // 更新current_gen_limit args.current_gen_limit = generation_limit (args.free_list_gen_number); } // 有下一個segment的時候繼續處理下一個segment, 不然跳出 if (heap_segment_next_rw (current_heap_segment)) { current_heap_segment = heap_segment_next_rw (current_heap_segment); current_brick = brick_of (heap_segment_mem (current_heap_segment)); end_brick = brick_of (heap_segment_allocated (current_heap_segment)-1); continue; } else { break; } } { // 若是brick中保存了對plug樹的偏移值則 // 調用make_free_list_in_brick // 設置brick到地址最大的plug // 不然設置設爲-1 (把-2, -3等等的都改成-1) int brick_entry = brick_table [ current_brick ]; if ((brick_entry >= 0)) { make_free_list_in_brick (brick_address (current_brick) + brick_entry-1, &args); dprintf(3,("Fixing brick entry %Ix to %Ix", current_brick, (size_t)args.highest_plug)); set_brick (current_brick, (args.highest_plug - brick_address (current_brick))); } else { if ((brick_entry > -32768)) { #ifdef _DEBUG ptrdiff_t offset = brick_of (args.highest_plug) - current_brick; if ((brick_entry != -32767) && (! ((offset == brick_entry)))) { assert ((brick_entry == -1)); } #endif //_DEBUG //init to -1 for faster find_first_object set_brick (current_brick, -1); } } } current_brick++; } } { // 設置剩餘的代邊界 int bottom_gen = 0; args.free_list_gen_number--; while (args.free_list_gen_number >= bottom_gen) { uint8_t* gap = 0; generation* gen2 = generation_of (args.free_list_gen_number); // 保證代中最少有一個對象 gap = allocate_at_end (Align(min_obj_size)); generation_allocation_start (gen2) = gap; // 設置代邊界 reset_allocation_pointers (gen2, gap); dprintf(3,("Fixing generation start of %d to: %Ix", args.free_list_gen_number, (size_t)gap)); PREFIX_ASSUME(gap != NULL); // 代中第一個對象應該是free object make_unused_array (gap, Align (min_obj_size)); args.free_list_gen_number--; } // 更新alloc_allocated成員到gen 0的開始邊界 //reset the allocated size uint8_t* start2 = generation_allocation_start (youngest_generation); alloc_allocated = start2 + Align (size (start2)); } // 統計清掃階段的結束時間 #ifdef TIME_GC finish = GetCycleCount32(); sweep_time = finish - start; #endif //TIME_GC }
gc_heap::make_free_list_in_brick
函數的代碼以下:
void gc_heap::make_free_list_in_brick (uint8_t* tree, make_free_args* args) { assert ((tree != NULL)); { int right_node = node_right_child (tree); int left_node = node_left_child (tree); args->highest_plug = 0; if (! (0 == tree)) { // 處理左邊的節點 if (! (0 == left_node)) { make_free_list_in_brick (tree + left_node, args); } // 處理當前節點 { uint8_t* plug = tree; // 當前plug前面的空餘空間 size_t gap_size = node_gap_size (tree); // 空餘空間的開始 uint8_t* gap = (plug - gap_size); dprintf (3,("Making free list %Ix len %d in %d", //dprintf (3,("F: %Ix len %Ix in %d", (size_t)gap, gap_size, args->free_list_gen_number)); // 記錄當前brick中地址最大的plug args->highest_plug = tree; #ifdef SHORT_PLUGS if (is_plug_padded (plug)) { dprintf (3, ("%Ix padded", plug)); clear_plug_padded (plug); } #endif //SHORT_PLUGS gen_crossing: { // 若是current_gen_limit等於MAX_PTR,表示咱們須要先決定gen 2的邊界 // 若是plug >= args->current_gen_limit而且plug在ephemeral heap segment,表示咱們須要決定gen 1或gen 0的邊界 // 決定的流程以下 // - 第一次current_gen_limit == MAX_PTR,在處理全部對象以前決定gen 2的邊界 // - 第二次plug超過了generation_allocation_start (generation_of ((gen_number - 2)))而且在ephemeral heap segment中,決定gen 1的邊界 // - 由於plug不會超過heap_segment_reserved (ephemeral_heap_segment),第三次會在上面的"設置剩餘的代邊界"中決定gen 0的邊界 if ((args->current_gen_limit == MAX_PTR) || ((plug >= args->current_gen_limit) && ephemeral_pointer_p (plug))) { dprintf(3,(" Crossing Generation boundary at %Ix", (size_t)args->current_gen_limit)); // 在處理全部對象以前決定gen 2的邊界時,不須要減1 if (!(args->current_gen_limit == MAX_PTR)) { args->free_list_gen_number--; args->free_list_gen = generation_of (args->free_list_gen_number); } dprintf(3,( " Fixing generation start of %d to: %Ix", args->free_list_gen_number, (size_t)gap)); // 決定代邊界 reset_allocation_pointers (args->free_list_gen, gap); // 更新current_gen_limit用於決定下一個代的邊界 args->current_gen_limit = generation_limit (args->free_list_gen_number); // 保證代中最少有一個對象 // 若是這個gap比較大(大於最小對象大小 * 2),剩餘的空間還能夠在下面放到free list中 if ((gap_size >= (2*Align (min_obj_size)))) { dprintf(3,(" Splitting the gap in two %Id left", gap_size)); make_unused_array (gap, Align(min_obj_size)); gap_size = (gap_size - Align(min_obj_size)); gap = (gap + Align(min_obj_size)); } else { make_unused_array (gap, gap_size); gap_size = 0; } goto gen_crossing; } } // 加到free list中 thread_gap (gap, gap_size, args->free_list_gen); add_gen_free (args->free_list_gen->gen_num, gap_size); } // 處理右邊的節點 if (! (0 == right_node)) { make_free_list_in_brick (tree + right_node, args); } } } }
壓縮階段結束之後還須要作一些收尾工做,請從上面plan_phase
中的recover_saved_pinned_info();
繼續看。
https://github.com/dotnet/coreclr/blob/master/Documentation/botr/garbage-collection.md
https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcimpl.h
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcpriv.h
https://github.com/dotnet/coreclr/issues/8959
https://github.com/dotnet/coreclr/issues/8995
https://github.com/dotnet/coreclr/issues/9053
https://github.com/dotnet/coreclr/issues/10137
https://github.com/dotnet/coreclr/issues/10305
https://github.com/dotnet/coreclr/issues/10141
GC的實際處理遠遠比文檔和書中寫的要複雜,但願這一篇文章可讓你更加深刻的理解CoreCLR,若是你發現了錯誤或者有疑問的地方請指出來,
另外這篇文章有一些部分還沒有涵蓋到,例如SuspendEE的原理,後臺GC的處理和stackwalking等,但願之後能夠再花時間去研究它們。
下一篇我將會實際使用LLDB跟蹤GC收集垃圾的處理,再下一篇會寫JIT相關的內容,敬請期待。