gc 不会立即回收new创建的对象,而是在内存压力等条件下触发分代回收;gen 0/1/2成本差异大,盲目调用gc.collect()反而拖慢程序;需注意引用管理、异步陷阱及loh碎片监控。

GC 不会因为你调用了 new 后就立刻回收,也不会在变量“离开作用域”的那一行释放内存——它只在内存压力、分配失败或特定代满载时触发,且不同代的代价天差地别。盲目调用 GC.Collect() 多数时候是在拖慢程序,而不是帮它。
Gen 0 / Gen 1 / Gen 2 回收成本差异极大
分代不是设计装饰,而是直接影响 STW 时间和吞吐量的真实机制:
-
Gen 0回收快(通常 new byte[1024]、List<int></int>)在此消亡 -
Gen 1是缓冲带,存活对象晋升至此;回收频率中等,暂停时间略长 -
Gen 2回收需遍历整个托管堆 + LOH,一次可能卡住 100–500ms,尤其在 Web API 响应中极易引发超时 - 你无法靠“多 new 几次”预测
Gen 2何时发生——它由系统内存压力或显式调用触发,不按分配节奏走
大对象堆(LOH)是隐性卡顿源
所有 ≥ 85,000 字节的对象(例如 new byte[85_000]、new double[10_000])直接进入 LOH,归属 Gen 2,且默认不压缩:
- LOH 碎片化后,即使总空闲空间充足,也可能因找不到连续块而被迫触发
Gen 2回收 - .NET 5+ 支持
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce,但仅生效一次,需手动触发 - 避免反复扩容:用
new List<byte>(estimatedSize)</byte>替代默认构造后Add,减少中间小对象和潜在 LOH 分配
什么时候能/该调用 GC.Collect()?
绝大多数场景下,让 GC 自行调度是最优解。人工干预只在极少数可量化收益的节点有意义,且必须配合内存监控验证:
- ✅ 可考虑
GC.Collect(0):长周期图像帧处理循环中,确认Gen 0滞后导致内存陡升(如内存曲线呈锯齿状上升) - ❌ 禁止
GC.Collect()或GC.Collect(2):HTTP 请求处理、UI 事件回调、构造函数中——这等于主动引入 STW,破坏响应性 - ⚠️
GC.SuppressFinalize()仅用于实现IDisposable的类型,在Dispose()内调用,防止 Finalizer 队列重复执行,和内存回收无直接关系
真正有效的优化不在“催 GC”,而在“少产垃圾”
比任何一次 GC.Collect() 更管用的动作:
- 复用缓冲区:
ArrayPool<byte>.Shared.Rent(4096)</byte>替代反复new byte[4096] - 避免闭包捕获大对象:把
var largeData = LoadBigArray(); Task.Run(() => Process(largeData))改为显式传参,防止委托长期持有引用 - 及时清空集合引用:
cache.Clear(); cache = null;(尤其静态集合),切断根引用链 - 慎用
async void和未 await 的 Task:它们可能延长局部对象生命周期,意外拖入更高代
LOH 碎片和 Gen 2 卡顿往往在压测后期才暴露,监控 Gen 2 回收频次与 STW 时间比看内存泄漏更可靠;别等超时了才翻 GC 日志。











