.NET Core 3.0 可卸載程序集原理簡析

由於最近在羣裏被問到如何理解 .NET Core 3.0 可卸載程序集,因此就寫了這篇簡單的分析。
由於時間實在不多,這篇文章只簡單的羅列了相關的代碼,請配合官方說明文檔理解。
另外,書籍《.NET Core 底層原理》預計 11 月出版,出版社比較拖 :O。git

連接

可卸載程序集的官方說明文檔以下:github

程序集中的 IL 代碼通過 JIT 編譯後,會儲存原生代碼在 LoaderAllocator 管理的 Code Heap 中,LoaderAllocator 的代碼地址以下:app

負責分配原生代碼的是 CodeManager ,代碼地址以下:dom

GC 實現代碼以下:ui

對象頭 (MethodTable) 代碼以下:code

AssemblyLoadContext 的代碼以下:對象

https://github.com/dotnet/coreclr/blob/release/3.0/src/System.Private.CoreLib/shared/System/Runtime/Loader/AssemblyLoadContext.csblog

分析

在 .NET Core 中咱們不能新建 AppDomain (儘管有默認的幾個 AppDomain),程序集會經過 AssemblyLoadContext 管理。簡單的來講,AssemblyLoadContext 負責管理有依賴關係的一組程序集,例如程序集 A 依賴程序集 B,那麼 A 和 B 須要使用同一個 AssemblyLoadContext 加載。每一個 AssemblyLoadContext 都會關聯不一樣的 LoaderAllocator,也就是擁有不一樣的 Code Heap。資源

.NET Core 3.0 開始容許卸載用戶建立的 AssemblyLoadContext ,也就是回收 AssemblyLoadContext 爲程序集分配的各類資源,包括 JIT 生成的原生代碼,PreCode,類型元數據等,流程大體以下:pdo

  • 用戶建立 AssemblyLoadContext (isCollectible = true)
  • 用戶使用 AssemblyLoadContext 加載程序集 A
  • 用戶使用 AssemblyLoadContext 加載程序集 B
  • 用戶建立程序集 A 和 B 中的類型的實例,並執行其中的方法
  • 用戶卸載 AssemblyLoadContext
  • .NET Core 等待全部程序集 A 和 B 中的類型的實例都被回收後,釋放 AssemblyLoadContext 管理的 LoaderAllocator 分配的資源

能夠參考下圖理解 (這是通過簡化的流程,詳細流程能夠看前面給出的官方說明文檔連接):

卸載 AssemblyLoadContext 時,取消對 LoaderAllocator 的關聯的代碼以下:

https://github.com/dotnet/coreclr/blob/release/3.0/src/binder/clrprivbinderassemblyloadcontext.cpp#L276

GC 標記對象時,同時標記關聯的 LoaderAllocator 的代碼以下 (在 gc.cpp 裏面):

#define go_through_object_cl(mt,o,size,parm,exp)                            \
{                                                                           \
    // 若是對象的 MethodTable 是由可回收的 AssemblyLoadContext 加載的
    if (header(o)->Collectible())                                           \
    {                                                                       \
        // 獲取關聯的 LoaderAllocator
        uint8_t* class_obj = get_class_object (o);                             \
        uint8_t** parm = &class_obj;                                           \
        // 標記 LoaderAllocator (根據 exp 的具體邏輯而定)
        do {exp} while (false);                                             \
    }                                                                       \
    // 若是對象包含引用類型的成員
    if (header(o)->ContainsPointers())                                      \
    {                                                                       \
        go_through_object_nostart(mt,o,size,parm,exp);                      \
    }                                                                       \
}
// 調用 MethodTable::GetLoaderAllocatorObjectForGC
#define get_class_object(i) GCToEEInterface::GetLoaderAllocatorObjectForGC((Object *)i)

LoaderAllocator 被回收之後到釋放資源的相關代碼 (被回收以前的邏輯參考官方說明文檔) :

說明就到此爲止了。你可能會奇怪爲何這篇文章沒有提到 Assembly 和 DomainAssembly ,這是由於它們在可卸載程序集的實現中並不重要,資源是經過 AssemblyLoadContext 關聯的 LoaderAllocator 統一分配和釋放的,與其說是可卸載程序集,不如說是可卸載程序集加載上下文 (AssemblyLoadContext)。

相關文章
相關標籤/搜索