.NET Core 3.0 可卸载程序集原理简析

文章转载授权级别:A       预计阅读时间:8分钟      损失发量:不好统计

因为最近在群里被问到如何理解 .NET Core 3.0 可卸载程序集,所以就写了这篇简单的分析。

因为时间实在很少,这篇文章只简单的罗列了相关的代码,请配合官方说明文档理解。

另外,书籍《.NET Core 底层原理》预计 11 月出版,出版社比较拖 :O。

Azulx: 昨晚下班群里问的农神,今早就出来了,这效率。 

链接

可卸载程序集的官方说明文档如下:

  • https://github.com/dotnet/coreclr/blob/release/3.0/Documentation/design-docs/unloadability.md

程序集中的 IL 代码经过 JIT 编译后,会储存原生代码在 LoaderAllocator 管理的 Code Heap 中,LoaderAllocator 的代码地址如下:

  • https://github.com/dotnet/coreclr/blob/release/3.0/src/vm/loaderallocator.hpp

  • https://github.com/dotnet/coreclr/blob/release/3.0/src/vm/loaderallocator.cpp

  • https://github.com/dotnet/coreclr/blob/release/3.0/src/vm/loaderallocator.inl

负责分配原生代码的是 CodeManager ,代码地址如下:

  • https://github.com/dotnet/coreclr/blob/release/3.0/src/vm/codeman.h

  • https://github.com/dotnet/coreclr/blob/release/3.0/src/vm/codeman.cpp

GC 实现代码如下:

  • https://raw.githubusercontent.com/dotnet/coreclr/release/3.0/src/gc/gc.cpp

对象头 (MethodTable) 代码如下:

  • https://github.com/dotnet/coreclr/blob/release/3.0/src/vm/methodtable.h

  • https://github.com/dotnet/coreclr/blob/release/3.0/src/vm/methodtable.inl

  • https://github.com/dotnet/coreclr/blob/release/3.0/src/vm/methodtable.cpp

AssemblyLoadContext 的代码如下:

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

分析

在 .NET Core 中我们不能新建 AppDomain (尽管有默认的几个 AppDomain),程序集会通过 AssemblyLoadContext 管理。简单的来说,AssemblyLoadContext 负责管理有依赖关系的一组程序集,例如程序集 A 依赖程序集 B,那么 A 和 B 需要使用同一个 AssemblyLoadContext 加载。每个 AssemblyLoadContext 都会关联不同的 LoaderAllocator,也就是拥有不同的 Code Heap。

.NET Core 3.0 开始允许卸载用户创建的 AssemblyLoadContext ,也就是回收 AssemblyLoadContext 为程序集分配的各种资源,包括 JIT 生成的原生代码,PreCode,类型元数据等,流程大致如下:

  • 用户创建 AssemblyLoadContext (isCollectible = true)

  • 用户使用 AssemblyLoadContext 加载程序集 A

  • 用户使用 AssemblyLoadContext 加载程序集 B

  • 用户创建程序集 A 和 B 中的类型的实例,并执行其中的方法

  • 用户卸载 AssemblyLoadContext

  • .NET Core 等待所有程序集 A 和 B 中的类型的实例都被回收后,释放 AssemblyLoadContext 管理的 LoaderAllocator 分配的资源

可以参考下图理解 (这是经过简化的流程,详细流程可以看前面给出的官方说明文档链接):

640?wx_fmt=png

卸载 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 被回收以后到释放资源的相关代码 (被回收之前的逻辑参考官方说明文档):

  • https://github.com/dotnet/coreclr/blob/release/3.0/src/vm/loaderallocator.cpp#L520

  • https://github.com/dotnet/coreclr/blob/release/3.0/src/vm/appdomain.cpp#L857

  • https://github.com/dotnet/coreclr/blob/release/3.0/src/vm/appdomain.cpp#L817

  • https://github.com/dotnet/coreclr/blob/release/3.0/src/vm/appdomain.hpp#L3086

  • https://github.com/dotnet/coreclr/blob/release/3.0/src/vm/appdomain.cpp#L6240

  • https://github.com/dotnet/coreclr/blob/release/3.0/src/vm/appdomain.cpp#L6283

  • https://github.com/dotnet/coreclr/blob/release/3.0/src/vm/loaderallocator.cpp#L88

  • https://github.com/dotnet/coreclr/blob/release/3.0/src/vm/loaderallocator.cpp#L1301  

说明就到此为止了。你可能会奇怪为什么这篇文章没有提到 Assembly 和 DomainAssembly ,这是因为它们在可卸载程序集的实现中并不重要,资源是通过 AssemblyLoadContext 关联的 LoaderAllocator 统一分配和释放的,与其说是可卸载程序集,不如说是可卸载程序集加载上下文 (AssemblyLoadContext)。 

https://github.com/dotnetcore640?wx_fmt=gif

打赏一杯酒,削减三分愁。跟着我们走,脱发包你有。

640?wx_fmt=png

640?wx_fmt=png

组织打赏账户为柠檬的账户,请标注「NCC」,并留下您的名字,以下地址可查看收支明细:https://github.com/dotnetcore/Home/blob/master/Statement-of-Income-and-Expense.md640?wx_fmt=png640?wx_fmt=png
OpenNCC,专注.NET技术的公众号
https://www.dotnetcore.xyz
640?wx_fmt=png
640?wx_fmt=png微信ID:OpenNCC
640?wx_fmt=png长按左侧二维码关注

欢迎打赏组织

给予我们更多的支持

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值