C# .NETGC

分代假设:读懂 .NET GC 回收策略的主线

很多人对 GC 的印象是"它是个黑盒,卡了就是 GC 的锅"。其实 .NET 的回收策略建立在一个非常朴素的观察上:绝大多数对象活得很短。方法里 new 的临时对象,出了作用域就是垃圾;只有少数配置、缓存之类的对象能活到最后。

既然如此,就没必要每次都扫全堆。托管堆被切成三代:新对象进 Gen0,熬过一次回收升 Gen1,再熬升 Gen2。回收 Gen0 便宜(对象少、引用少),回收 Gen2 贵(全堆扫)。这个设计的赌注是:大部分垃圾在 Gen0 就被收掉了,Gen2 的回收频率可以很低。

整体链路示意
整体链路示意

另有一块特殊区域叫大对象堆(LOH),超过 85KB 的对象直接进去,不经历年轻代。这带来一个非常实用的推论:频繁分配大数组/大字符串,等于把最贵的回收(Gen2/LOH)当日常。我们有个导出服务拼 10MB 的字符串报表,每次导出都诱发一次全堆回收——改成流式写出之后,GC 时间直接归零。

日常调优就三句话:

GC 不是敌人,分配习惯才是。把"朝生夕死"这条假设当设计原则来遵守——对象尽量小、尽量短命——GC 自然安静。