由于最近在群里被问到如何理解 .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)。