资讯动态

01-06-认知篇-总览-五大GC全景对比

发布时间:2026/10/2 2:26:07 来源:尧图企业网站定制
五大 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 GCIL2CPP、Unity Incremental GC、.NET Core GC、Server GC——进行全景式对比从算法模型、分代策略、并发能力、停顿特征、适用场景等维度展开分析。目标是帮助你在不同项目条件下选择最合适的 GC 策略。二、Mono SGen GCMono SGenSimple 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 GCIL2CPPBoehm GC 是一个经典的保守式标记-清除垃圾收集器在 Unity 使用 IL2CPP 后端时作为默认 GC。Boehm 的设计哲学是简单和可移植性——它不需要编译器的精确类型信息通过扫描栈和寄存器中的值来识别可能的指针保守识别因此可以与任何 C/C 代码配合工作。Boehm 的核心特征是非分代、非压缩、保守式。非分代意味着每次 GC 都是全堆扫描没有新生代/老年代的区分非压缩意味着 GC 不会移动对象来消除碎片堆中的空洞只能通过空闲链表来复用保守式意味着它可能将非指针的整数值误认为指针导致本应被回收的对象被保留虚假引用。特性Boehm GC影响分代支持❌ 不支持全堆扫描停顿与堆大小成正比压缩支持❌ 不支持堆碎片化无法自动修复精确/保守保守式可能存在虚假引用内存泄漏风险并发标记✅ 支持可减少标记阶段停顿移动对象❌ 不移动指针固定无需更新引用适用场景IL2CPP 默认跨平台兼容性好Boehm 的优势在于其简单性和兼容性。因为它不需要精确的类型信息所以可以与 IL2CPP 生成的 C 代码无缝配合——IL2CPP 将 C# 转换为 CBoehm 可以直接扫描 C 栈来寻找引用。这种设计避免了在 IL2CPP 中实现精确 GC 的复杂性。Boehm 的劣势同样明显。非分代意味着即使你只分配了一个小对象GC 也可能扫描整个堆非压缩意味着长期运行后堆碎片化会越来越严重最终导致可用空间不足但实际已释放的内存无法复用。在大型项目中Boehm GC 的停顿时间可能达到几十甚至上百毫秒严重影响帧率。四、Unity Incremental GCUnity 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-100ms3-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老年代加上大对象堆LOHLarge Object Heap和小对象堆SOHSmall 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 的一个重要优势是后台 GCBackground GC。在后台 GC 模式下Gen2 的标记和清除可以在用户线程运行的同时进行只在需要移动对象压缩时短暂暂停。这使得 .NET Core GC 在大堆场景下的停顿时间远小于 Boehm GC——即使堆有几百 MBGen2 回收的停顿通常也只有几毫秒。.NET Core GC 支持两种工作模式Workstation GC工作站模式和Server GC服务器模式。Workstation GC 针对客户端应用优化使用一个 GC 线程注重低延迟Server GC 针对服务器应用优化使用多个 GC 线程并行回收注重高吞吐量。这两种模式在下一节详细讨论。六、Server GCServer 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 GCGC 线程数1 CPU 核心数堆分区数1 CPU 核心数回收方式串行/并发并行吞吐量中高停顿时间较短较短并行更快内存占用低高多堆分区适用场景客户端/桌面服务器/后端配置方式默认ServerGarbageCollectiontrue/ServerGarbageCollectionServer 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 实现代表了不同的设计哲学和工程权衡Mono SGen分代但受限于 Mono 运行时适合 Unity Mono 后端的小型项目。Boehm GC简单兼容但非分代非压缩是 Unity IL2CPP 的默认选择大型项目需要配合 Incremental GC。Unity Incremental GCBoehm 的增量模式通过时间切片降低单次停顿是 IL2CPP 项目的首选改进。.NET Core GC分代压缩并发现代 GC 设计的标杆在 Unity CoreCLR 和 .NET 服务中表现优异。Server GC.NET Core GC 的并行模式为多核服务器场景优化吞吐量。选择 GC 策略的核心原则是先评估项目条件运行时、平台、堆大小、分配模式再选择最匹配的 GC最后通过 Profiler 验证效果。没有最好的GC只有最适合的 GC。在后续章节中我们将逐一深入每种 GC 的算法细节和调优方法。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑