1.背景:
.NET Framework 的 CLR(公共语言运行时,Common Language Runtime)的两种不同构建版本:Workstation(工作站版本) 和 Server(服务器版本)。这两个版本在底层实现上存在差异,主要针对不同的应用场景进行优化。
在早期 .NET Framework(如 2.0-3.5)中,CLR 的宿主(Hosting)机制通过不同的非托管代码模块实现,具体表现为:
- mscorwks.dll(Microsoft Common Object Runtime WorkStation):工作站版本的 CLR 核心模块。
- mscorsvr.dll(Microsoft Common Object Runtime Server):服务器版本的 CLR 核心模块。
这些模块由 C++ 编写,属于非托管代码,负责 CLR 的启动、内存管理(GC)、线程调度等核心功能
1.非托管入口点
- CLR 的启动由非托管代码(C++)实现,入口模块为 mscoree.dll(Microsoft Component Object Runtime Execution Engine)。
- mscoree.dll 负责加载 mscorwks.dll 或 mscorsvr.dll,具体取决于配置。
1.显示配置
通过配置文件(app.config)指定- <configuration>
- <runtime>
- <gcServer enabled="true"/>
- <gcConcurrent enabled="true"/>
- </runtime>
- </configuration>
复制代码
- 或通过环境变量 COMPLUS_gcServer 设置为 1。
2.隐式推断:
若未显式配置,CLR 根据进程类型自动选择:
控制台应用、WinForms/WPF 默认使用 Workstation。
ASP.NET、Windows 服务等默认使用 Server(取决于宿主配置)。
3.NET Framework 4.0 及之后:
- mscorwks.dll 和 mscorsvr.dll 合并为 clr.dll,但两种 GC 模式(Workstation/Server)仍然保留。
- 通过配置选择 GC 模式,而非直接依赖不同的 DLL。
4.NET Core / .NET 5+:
完全重构的运行时(CoreCLR/CoreRT),不再区分 Workstation/Server 的 DLL。
但 GC 模式(Workstation/Server)仍然存在,可通过配置选择:- dotnet yourapp.dll --GCName=Server
复制代码- Console.WriteLine($"当前GC模式: {(GCSettings.IsServerGC ? "Server" : "Workstation")}");
复制代码 一般启用 Server GC 是利用多核并行性,同时在性能观察中,可以监控 GC 暂停时间,必要时调整 GCHeapCount 或 GCHeapAffinity。
2.目标场景
- Workstation(工作站版本):
- 优化目标:交互性(低延迟)和 单线程性能。
- 适用场景:客户端应用程序(如桌面应用、WinForms/WPF)、需要快速响应的场景。
- Server(服务器版本):
- 优化目标:高吞吐量 和 多线程并行性。
- 适用场景:服务器端应用程序(如 ASP.NET、后台服务)、多核 CPU 密集型任务。
Workstation GC:提供了一种默认的并发GC模式(当然这个是可以选择关掉)
- 并发 GC(Concurrent GC):允许用户线程与 GC 线程交替执行,减少暂停时间。
- 适合需要低延迟的交互式应用,避免界面卡顿
Server GC:
- 多堆并行回收:
- 为每个逻辑 CPU 核心分配独立的堆(Heap),减少线程竞争。
- 使用专用 GC 线程(每个 CPU 核心一个线程),并行回收垃圾。
- 适合高吞吐量的多线程服务,最大化 CPU 利用率。
线程池调度
- Workstation:
- Server:
- 线程池初始分配更多线程(与 CPU 核心数相关),适应高并发请求。
3.分代GC:内存管理的基础结构
1.核心设计
分代模型是 .NET GC 的核心设计,基于以下假设:
- 弱分代假设(Weak Generational Hypothesis):大多数对象生命周期短暂,存活时间较短。
- 强分代假设(Strong Generational Hypothesis):存活时间越长的对象,越不容易被回收。
因此,.NET 将堆内存划分为三个代:
- 第0代(Gen 0):存放最新创建的对象。
- 第1代(Gen 1):存放从 Gen 0 晋升的存活对象。
- 第2代(Gen 2):存放从 Gen 1 晋升的长生命周期对象。
2.分代GC的工作流程
- 新对象分配:所有新对象初始分配在 Gen 0。
- Gen 0 回收:
- 当 Gen 0 满时触发回收。
- 存活的对象晋升到 Gen 1。
- 回收快速且频率高(通常仅需几毫秒)。
- Gen 1 回收:
- 若 Gen 0 回收后 Gen 1 仍满,触发 Gen 1 回收。
- 存活对象晋升到 Gen 2。
- Gen 2 回收:
- 当 Gen 2 满时触发,回收耗时较长(可能数十到数百毫秒)。
- 存活对象保留在 Gen 2。
分代模型的优势在于减少扫描范围,避免每次回收都遍历整个堆
4.并发GC:减少暂停时间的执行策略
并发 GC 是分代模型的一种补充优化,目标是最小化应用程序线程的暂停时间(Stop-The-World, STW)。
其核心思想是:在 GC 回收过程中,允许应用程序线程继续执行。
1.并发GC的具体实现
在 .NET 的 Workstation GC(工作站模式) 中:
- 仅作用于 Gen 2 的回收:
- Gen 0/1 的回收时间极短,直接采用 STW 暂停。
- Gen 2 的回收耗时较长,因此使用并发模式。
- 并发阶段流程:
- 初始标记(Initial Marking):短暂暂停,标记根对象。
- 并发标记(Concurrent Marking):应用程序线程与 GC 线程并发执行。
- 重新标记(Final Marking):短暂暂停,修正并发期间对象状态变化。
- 并发清理(Concurrent Sweep):回收不可达内存,应用程序线程继续运行。
2.并发GC的局限性
- 内存碎片:并发清理可能导致内存碎片化。
- CPU 资源竞争:GC 线程与应用程序线程共享 CPU 资源。
3.可以关闭并发GC
行为并发 GC(默认)非并发 GC(禁用并发)Gen 2 回收暂停时间较短(分阶段暂停)较长(完全暂停)CPU 资源占用GC 线程与应用线程共享 CPUGC 独占 CPU,回收更快完成适用场景交互式应用(如 UI 程序)批处理任务、后台服务(允许较长暂停)
需要说明的是:NET5中是这样设
- / 设置延迟模式为 Batch(禁用并发 GC)
- GCSettings.LatencyMode = GCLatencyMode.Batch;
复制代码
禁用并发 GC 的适用场景
a. 高吞吐批处理任务
例如数据转换、离线计算等任务,允许完全暂停以换取更快的 GC 完成速度。
优势:减少 GC 线程与应用线程的 CPU 竞争,提高整体吞吐量。
b. 内存密集型后台服务
若服务对延迟不敏感,但需要快速释放内存。
示例:日志处理服务、文件压缩服务。
c. 调试与分析
禁用并发 GC 可以使 GC 行为更可预测,便于调试内存问题。
5.服务器模式 GC 的核心设计
服务器模式 GC 的底层逻辑是:
- 为每个逻辑 CPU 核心分配独立的堆(Heap),每个堆包含 Gen 0、Gen 1 和 Gen 2。
- 为每个堆分配专用的 GC 线程,并行执行垃圾回收。
- 共享大对象堆(LOH, Large Object Heap),所有堆共享同一个 LOH。
在服务器模式下,Gen 2 的回收是并行的,但实现方式与 Gen 0/1 不同
- 全局触发:当任意堆的 Gen 2 满时,触发全局 Gen 2 回收。
- 多线程协同:
- 分段标记(Segment Marking):将 Gen 2 堆划分为多个逻辑段,每个 GC 线程负责一个段的标记。
- 并行压缩(Parallel Compaction):多个线程并行移动存活对象,减少内存碎片。
- 完全暂停(Stop-The-World):
- Gen 2 回收期间,所有应用程序线程暂停。
- 但 GC 线程之间并行工作,最大化利用多核 CPU。
6.总结:
1. 工作站模式(Workstation GC)
a. Gen 0/1 回收
<ul>STW(完全暂停):无论是否启用并发 GC,Gen 0/1 的回收均会完全暂停用户线程。
原因:Gen 0/1 回收极快(通常 |