在这篇中我将讲述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相关的内容,敬请期待。