由於最近在羣裏被問到如何理解 .NET Core 3.0 可卸載程序集,因此就寫了這篇簡單的分析。
由於時間實在不多,這篇文章只簡單的羅列了相關的代碼,請配合官方說明文檔理解。
另外,書籍《.NET Core 底層原理》預計 11 月出版,出版社比較拖 :O。git
可卸載程序集的官方說明文檔以下:github
程序集中的 IL 代碼通過 JIT 編譯後,會儲存原生代碼在 LoaderAllocator 管理的 Code Heap 中,LoaderAllocator 的代碼地址以下:app
負責分配原生代碼的是 CodeManager ,代碼地址以下:dom
GC 實現代碼以下:ui
對象頭 (MethodTable) 代碼以下:code
AssemblyLoadContext 的代碼以下:對象
在 .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 時,取消對 LoaderAllocator 的關聯的代碼以下:
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)。