☰
01-06-认知篇-总览-五大GC全景对比
2026/10/2 2:25:56 网站建设 项目流程

五大 GC 全景对比

篇章:01-认知篇 · 总览
阅读时间:约 30 分钟
前置知识:了解 GC 基本概念


一、引言

在 Unity 和 .NET 生态中,存在多种 GC 实现,每种都有其设计目标和适用场景。开发者在面对性能问题时,经常需要在这些 GC 之间做出选择——是使用 Unity 默认的 Boehm GC,还是启用 Incremental GC?是坚持 Mono 后端,还是迁移到 IL2CPP + SGen?如果脱离 Unity 到纯 .NET 环境,.NET Core GC 和 Server GC 又有何不同?

本文将对五种主流 GC 实现——Mono SGen GC、Boehm GC(IL2CPP)、Unity Incremental GC、.NET Core GC、Server GC——进行全景式对比,从算法模型、分代策略、并发能力、停顿特征、适用场景等维度展开分析。目标是帮助你在不同项目条件下,选择最合适的 GC 策略。

二、Mono SGen GC

Mono SGen(Simple Generational GC)是 Mono 运行时的垃圾收集器,在 Unity 使用 Mono 后端时作为默认 GC。SGen 是一个分代式垃圾收集器,将堆分为两个代: nursery(新生代,又称 minor heap)和 old generation(老年代)。新对象分配在 nursery 中,经过一次 minor GC 后存活的对象会被提升到老年代。

SGen 的分代策略带来了一个关键优势:短生命周期对象的回收成本极低。在游戏开发中,大量临时对象(如每帧的临时数组、字符串拼接结果)都是短生命周期的,它们在 nursery 中分配,在下一次 minor GC 时被快速回收,无需扫描整个堆。这使得 SGen 在处理高频小对象分配时,性能远优于非分代的 Boehm GC。

然而,SGen 在 Unity 中的表现受到 Mono 运行时本身的限制。Mono 的 JIT 编译器在某些平台上的性能不如 IL2CPP 的 AOT 编译,且 Mono 运行时的维护已经逐渐停滞。此外,SGen 的 major GC(老年代回收)仍然是 Stop-The-World 的,当老年代填满时,全堆扫描的停顿可能很明显。SGen 支持压缩,但压缩操作也是 STW 的。

SGen 的另一个特性是支持concurrent mark(并发标记)模式,可以在用户线程运行的同时进行标记阶段,减少停顿时间。但在 Unity 的 Mono 后端中,这个特性并不总是被启用或调优到最佳状态。

三、Boehm GC(IL2CPP)

Boehm GC 是一个经典的保守式标记-清除垃圾收集器,在 Unity 使用 IL2CPP 后端时作为默认 GC。Boehm 的设计哲学是简单和可移植性——它不需要编译器的精确类型信息,通过扫描栈和寄存器中的值来识别可能的指针(保守识别),因此可以与任何 C/C++ 代码配合工作。

Boehm 的核心特征是:非分代、非压缩、保守式。非分代意味着每次 GC 都是全堆扫描,没有新生代/老年代的区分;非压缩意味着 GC 不会移动对象来消除碎片,堆中的空洞只能通过空闲链表来复用;保守式意味着它可能将非指针的整数值误认为指针,导致本应被回收的对象被保留(虚假引用)。

特性Boehm GC影响
分代支持❌ 不支持全堆扫描,停顿与堆大小成正比
压缩支持❌ 不支持堆碎片化无法自动修复
精确/保守保守式可能存在虚假引用,内存泄漏风险
并发标记✅ 支持可减少标记阶段停顿
移动对象❌ 不移动指针固定,无需更新引用
适用场景IL2CPP 默认跨平台兼容性好

Boehm 的优势在于其简单性和兼容性。因为它不需要精确的类型信息,所以可以与 IL2CPP 生成的 C++ 代码无缝配合——IL2CPP 将 C# 转换为 C++,Boehm 可以直接扫描 C++ 栈来寻找引用。这种设计避免了在 IL2CPP 中实现精确 GC 的复杂性。

Boehm 的劣势同样明显。非分代意味着即使你只分配了一个小对象,GC 也可能扫描整个堆;非压缩意味着长期运行后堆碎片化会越来越严重,最终导致可用空间不足但实际已释放的内存无法复用。在大型项目中,Boehm GC 的停顿时间可能达到几十甚至上百毫秒,严重影响帧率。

四、Unity Incremental GC

Unity Incremental GC(增量 GC)并非一个独立的 GC 算法,而是对 Boehm GC 的一种运行模式改进。它通过将一次大的 GC 停顿拆分为多个小的增量步骤,分散到多帧中执行,从而降低单次停顿对帧率的影响。

增量 GC 的核心机制是time-sliced marking(时间切片标记)。传统的 Boehm GC 在触发时,一次性完成整个堆的标记-清除,造成一个大的 STW 停顿。增量 GC 将标记阶段拆分为多个小步骤,每帧执行一小部分标记工作,通过多帧完成整个标记过程。在每帧的标记步骤中,GC 只占用预设的时间预算(默认约 3ms),剩余时间留给游戏逻辑和渲染。

增量 GC 的关键挑战是屏障(Barrier)机制。在增量标记过程中,用户线程可能修改对象引用关系——例如,将一个已标记为"存活"的对象的引用指向一个未标记的对象。为了处理这种并发修改,增量 GC 需要在写操作时插入屏障代码,记录被修改的引用,确保标记结果的正确性。这种屏障会带来少量的运行时开销,但远小于大停顿的影响。

指标Boehm GC(传统)Incremental GC
单次最大停顿10-100ms+3-6ms(可配置)
总 GC 耗时TT + 屏障开销(约 5-10%)
帧率稳定性差(周期性大卡顿)好(停顿分散)
实现复杂度低中(需屏障支持)
适用场景简单项目中大型项目

增量 GC 的效果取决于堆大小和分配模式。当堆较小时,增量标记可以在几帧内完成,停顿几乎不可感知;当堆很大(如数百 MB)时,增量标记可能需要很多帧才能完成一轮,期间新分配的对象可能使标记结果过时,导致需要重新开始。因此,增量 GC 虽然降低了单次停顿,但并不减少总工作量——它是一种"时间换空间"的策略,用更多的总时间来换取更小的单次停顿。

在 Unity 中启用增量 GC 非常简单:在 Project Settings → Player → Other Settings 中勾选 "Use Incremental GC",或通过代码PlayerSettings.SetIncrementalGcEnabled(true)设置。但启用后需要注意:屏障开销会增加少量每帧成本,且某些平台(如 WebGL)的增量 GC 行为可能有所不同。

五、.NET Core GC

.NET Core GC(也称为 .NET GC)是微软为 .NET 运行时开发的垃圾收集器,代表了现代 GC 设计的先进水平。它是一个分代式、压缩式、精确式的垃圾收集器,支持并发回收和多种工作模式。

.NET Core GC 将堆分为三个代:Gen0(新生代)、Gen1(中年代)、Gen2(老年代),加上大对象堆(LOH,Large Object Heap)和小对象堆(SOH,Small Object Heap)。Gen0 和 Gen1 合称为" ephemeral segment"(短命段),回收频率高、速度快;Gen2 回收频率低但耗时长。大对象(≥85,000 字节)直接分配在 LOH 上,LOH 不进行压缩(避免移动大对象的成本),但 .NET 5+ 可以选择启用 LOH 压缩。

特性.NET Core GC说明
分代3 代 + LOH短命对象快速回收
压缩✅ Gen0/Gen1/Gen2消除碎片化
LOH 压缩可选(.NET 5+)大对象可选压缩
并发回收✅ 后台 GCGen2 可并发回收
精确/保守精确式无虚假引用
工作模式Workstation/Server适应不同负载

.NET Core GC 的一个重要优势是后台 GC(Background GC)。在后台 GC 模式下,Gen2 的标记和清除可以在用户线程运行的同时进行,只在需要移动对象(压缩)时短暂暂停。这使得 .NET Core GC 在大堆场景下的停顿时间远小于 Boehm GC——即使堆有几百 MB,Gen2 回收的停顿通常也只有几毫秒。

.NET Core GC 支持两种工作模式:Workstation GC(工作站模式)和Server GC(服务器模式)。Workstation GC 针对客户端应用优化,使用一个 GC 线程,注重低延迟;Server GC 针对服务器应用优化,使用多个 GC 线程并行回收,注重高吞吐量。这两种模式在下一节详细讨论。

六、Server GC

Server GC 是 .NET Core GC 的一种配置模式,专为高吞吐量服务器场景设计。与 Workstation GC 相比,Server GC 的核心区别在于并行回收——它为每个 CPU 核心分配一个独立的堆和 GC 线程,回收时所有 GC 线程并行工作,充分利用多核能力。

Server GC 的工作原理是:每个逻辑 CPU 核心对应一个 GC 线程和一个堆分区(heap segment)。对象分配时,线程优先在自己的核心对应堆分区上分配,减少跨核心的缓存争用。GC 触发时,所有 GC 线程同时开始标记和清除,各自负责自己的堆分区,最后在压缩阶段协调引用更新。这种设计使得 Server GC 在多核机器上的回收速度远快于单线程的 Workstation GC。

维度Workstation GCServer GC
GC 线程数1= CPU 核心数
堆分区数1= CPU 核心数
回收方式串行/并发并行
吞吐量中高
停顿时间较短较短(并行更快)
内存占用低高(多堆分区)
适用场景客户端/桌面服务器/后端
配置方式默认<ServerGarbageCollection>true</ServerGarbageCollection>

Server GC 的代价是更高的内存占用。因为每个核心都有独立的堆分区,总堆空间是 Workstation GC 的 N 倍(N = 核心数)。在内存受限的环境中(如容器化部署),这可能成为问题。.NET Core 还提供了Server GC + Retained Memory模式(.NET 8+),可以限制 Server GC 的内存占用。

在 Unity 上下文中,Server GC 并不直接可用——Unity 使用 Mono 或 IL2CPP 运行时,而非 .NET Core 运行时。但如果你的项目涉及 Unity 与 .NET 后端服务的交互(如游戏服务器),理解 Server GC 的特性有助于你在服务端做出正确的 GC 配置选择。

七、五大 GC 对比矩阵

以下矩阵从多个维度全面对比五种 GC 实现:

维度Mono SGenBoehm (IL2CPP)Incremental GC.NET Core GCServer GC
分代支持✅ 2代❌❌✅ 3代+LOH✅ 3代+LOH
压缩支持✅❌❌✅✅
精确/保守精确保守保守精确精确
并发回收部分❌增量标记✅ 后台GC✅ 后台GC
并行回收❌❌❌❌✅ 多线程
STW 停顿中大小(分散)小小
总回收效率中低低(+屏障开销)高很高
堆碎片化可修复严重严重可修复可修复
内存占用中中中中高
适用平台Unity MonoUnity IL2CPPUnity IL2CPP.NET 应用.NET 服务器
成熟度中高中很高很高

从矩阵可以看出,.NET Core GC 在几乎所有维度上都优于 Unity 生态中的 GC 实现。这并不意外——.NET Core GC 是微软投入大量工程资源持续优化的产品,而 Unity 的 GC 选择受限于运行时(Mono/IL2CPP)的架构约束。Unity 在 2022+ 版本中逐步引入了 CoreCLR 支持(作为 IL2CPP 的替代),使得 .NET Core GC 可以在 Unity 中使用,这是一个重要的改进方向。

八、如何选择

GC 策略的选择取决于项目条件。以下是一个决策指南:

Unity 项目(Mono 后端):使用 SGen GC。SGen 的分代策略对游戏中的高频小对象分配友好。如果遇到 major GC 停顿问题,可以尝试调整 nursery 大小或启用 SGen 的并发标记模式。

Unity 项目(IL2CPP 后端):默认使用 Boehm GC。如果遇到 GC 停顿问题,首先启用 Incremental GC——这是最低成本的改进。如果增量 GC 仍不满足需求,评估堆大小是否可控——如果堆可以控制在 100MB 以内,Boehm + Incremental 的表现通常可接受;如果堆很大且持续增长,需要从代码层面减少分配和控制存活对象数量。

Unity 项目(CoreCLR 后端,2022+):使用 .NET Core GC。这是 Unity 生态中最好的 GC 体验——分代、压缩、并发回收一应俱全。如果你的 Unity 版本支持 CoreCLR,强烈建议迁移。

.NET 后端服务:默认使用 Server GC。如果服务部署在单核容器中或内存受限,切换到 Workstation GC。如果延迟敏感(如实时游戏服务器),评估 Background GC 的停顿表现,必要时调整 Gen0 预算。

项目场景推荐 GC理由
Unity Mono, 小型项目SGen分代回收,默认即可
Unity IL2CPP, 中型项目Boehm + Incremental增量分散停顿
Unity IL2CPP, 大型项目Boehm + Incremental + 代码优化堆控制是关键
Unity CoreCLR.NET Core GC全维度最优
.NET Web 服务器Server GC多核并行高吞吐
.NET 单核容器Workstation GC避免多堆内存浪费
.NET 实时游戏服务器Server GC + 调优平衡吞吐与延迟

九、总结

五大 GC 实现代表了不同的设计哲学和工程权衡:

  1. Mono SGen:分代但受限于 Mono 运行时,适合 Unity Mono 后端的小型项目。
  2. Boehm GC:简单兼容但非分代非压缩,是 Unity IL2CPP 的默认选择,大型项目需要配合 Incremental GC。
  3. Unity Incremental GC:Boehm 的增量模式,通过时间切片降低单次停顿,是 IL2CPP 项目的首选改进。
  4. .NET Core GC:分代+压缩+并发,现代 GC 设计的标杆,在 Unity CoreCLR 和 .NET 服务中表现优异。
  5. Server GC:.NET Core GC 的并行模式,为多核服务器场景优化吞吐量。

选择 GC 策略的核心原则是:先评估项目条件(运行时、平台、堆大小、分配模式),再选择最匹配的 GC,最后通过 Profiler 验证效果。没有"最好的"GC,只有"最适合"的 GC。在后续章节中,我们将逐一深入每种 GC 的算法细节和调优方法。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询