CoreCLR
在这篇中我将讲述GC Collector内部的实现, 这是CoreCLR中除了JIT以外最复杂部分,下面一些概念目前尚未有公开的文档和书籍讲到。
为了分析这部分我花了一个多月的时间,期间也多次向CoreCLR的开发组提问过,我有信心以下内容都是比较准确的,但如果你发现了错误或者有疑问的地方请指出来,
以下的内容基于CoreCLR 1.1.0的源代码分析,以后可能会有所改变。
因为内容过长,我分成了两篇,这一篇分析代码,下一篇实际使用LLDB跟踪GC收集垃圾的处理。
需要的预备知识
- 看过BOTR中GC设计的文档 原文 译文
- 看过我之前的系列文章, 碰到不明白的可以先跳过但最少需要看一遍
- 对c中的指针有一定了解
- 对常用数据结构有一定了解, 例如链表
- 对基础c++语法有一定了解, 高级语法和STL不需要因为微软只用了低级语法
GC的触发
GC一般在已预留的内存不够用或者已经分配量超过阈值时触发,场景包括:
不能给分配上下文指定新的空间时
当调用try_allocate_more_space不能从segment结尾或自由对象列表获取新的空间时会触发GC, 详细可以看我上一篇中分析的代码。
分配的数据量达到一定阈值时
阈值储存在各个heap的dd_min_gc_size(初始值), dd_desired_allocation(动态调整值), dd_new_allocation(消耗值)中,每次给分配上下文指定空间时会减少dd_new_allocation。
如果dd_new_allocation变为负数或者与dd_desired_allocation的比例小于一定值则触发GC,
触发完GC以后会重新调整dd_new_allocation到dd_desired_allocation。
参考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触发的更加频繁。
StressGC
允许手动设置特殊的GC触发策略, 参考这个文档
作为例子,你可以试着在运行程序前运行export COMPlus_GCStress=1
GCStrees会通过调用GCStress<gc_on_alloc>::MaybeTrigger(acontext)触发,
如果你设置了COMPlus_GCStressStart环境变量,在调用MaybeTrigger一定次数后会强制触发GC,另外还有COMPlus_GCStressStartAtJit等参数,请参考上面的文档。
默认StressGC不会启用。
手动触发GC
在.Net程序中使用GC.Collect可以触发手动触发GC,我相信你们都知道。
调用.Net中的GC.Collect会调用CoreCLR中的GCHeap::GarbageCollect => GarbageCollectTry => GarbageCollectGeneration。
GC的处理
以下函数大部分都在gc.cpp里,在这个文件里的函数我就不一一标出文件了。
GC的入口点
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
     
