C#的垃圾回收(GC)是.NET运行时自动管理内存的核心机制,主要负责回收不再被引用的对象占用的内存空间,减少开发者手动管理内存的负担。GC将托管堆中的对象按照存活时间划分为三个代,分别是Gen 0、Gen 1、Gen 2,不同代对应不同的回收策略和频率。

C# GC的基本工作原理
当程序创建新对象时,对象会被分配到托管堆的Gen 0区域。GC会定期触发回收操作,遍历所有被根对象(比如全局变量、栈上的局部变量、静态变量等)引用的对象,标记出仍然存活的对象,其余未被标记的对象就会被判定为垃圾,其占用的内存会被释放。
GC的回收过程分为标记、清除、压缩三个阶段:标记阶段找出所有存活对象;清除阶段释放垃圾对象的内存;压缩阶段会将存活的对象移动到连续的内存空间,减少内存碎片,同时更新所有引用这些对象的指针。
Gen 0、Gen 1、Gen 2的定义与特点
Gen 0(第0代)
Gen 0是对象最初被分配的区域,大部分对象的生命周期都很短,比如临时变量、循环内创建的对象等,这些对象通常会在Gen 0回收时被清理。Gen 0的大小相对较小,因此Gen 0的回收频率最高,但是回收速度也最快,因为需要遍历和处理的对象数量较少。
Gen 1(第1代)
当Gen 0回收时,仍然存活的对象会被晋升到Gen 1。Gen 1相当于一个缓冲区,存放那些存活时间略长于Gen 0对象、但又不会长期存在的对象。Gen 1的回收频率低于Gen 0,只有当Gen 0回收后空间仍然不足时,才会触发Gen 1的回收。
Gen 2(第2代)
Gen 1回收后仍然存活的对象会被晋升到Gen 2。Gen 2存放的是生命周期很长的对象,比如应用程序的全局单例、长期缓存的对象等。Gen 2的大小通常较大,回收频率最低,但是回收时需要遍历整个托管堆,因此耗时最长,对程序性能的影响也最大。
对象晋升规则
对象的晋升遵循以下逻辑:
- 新创建的对象首先进入Gen 0。
- Gen 0回收后存活的对象晋升到Gen 1。
- Gen 1回收后存活的对象晋升到Gen 2。
- Gen 2回收后存活的对象仍然留在Gen 2,因为已经没有更高的代可以晋升。
- 大对象(大小超过85000字节的对象)不会被分配到Gen 0,而是直接进入Gen 2的大对象堆(LOH),避免频繁复制大对象带来的性能开销。
不同代的回收触发条件
| 代别 | 触发条件 | 回收范围 |
|---|---|---|
| Gen 0回收 | Gen 0区域分配满;手动调用GC.Collect(0);系统内存压力大 | 仅回收Gen 0区域的对象 |
| Gen 1回收 | Gen 0回收后空间不足;手动调用GC.Collect(1);系统内存压力大 | 回收Gen 0和Gen 1区域的对象 |
| Gen 2回收 | Gen 1回收后空间不足;手动调用GC.Collect(2)或GC.Collect();系统内存压力大 | 回收所有代(Gen 0、Gen 1、Gen 2)以及大对象堆的对象 |
代码示例:观察GC代的行为
下面的代码可以演示对象的分配和GC回收的基本过程:
using System;
class Program
{
static void Main()
{
// 创建Gen 0对象
var tempObj = new object();
Console.WriteLine("初始对象代数:" + GC.GetGeneration(tempObj));
// 触发Gen 0回收
GC.Collect(0);
GC.WaitForPendingFinalizers();
Console.WriteLine("第一次Gen 0回收后对象代数:" + GC.GetGeneration(tempObj));
// 再次触发Gen 0回收,存活对象会晋升到Gen 1
GC.Collect(0);
GC.WaitForPendingFinalizers();
Console.WriteLine("第二次Gen 0回收后对象代数:" + GC.GetGeneration(tempObj));
// 触发Gen 1回收,存活对象会晋升到Gen 2
GC.Collect(1);
GC.WaitForPendingFinalizers();
Console.WriteLine("Gen 1回收后对象代数:" + GC.GetGeneration(tempObj));
}
}
运行上述代码可以看到,随着回收次数的增加,存活的对象的代数会逐渐晋升,最终停留在Gen 2。
开发中的注意事项
了解GC代的机制后,在开发中可以通过以下方式优化内存使用:
- 尽量避免创建不必要的长期存活对象,减少Gen 2的对象数量,降低Gen 2回收的频率。
- 不要频繁手动调用
GC.Collect(),尤其是全代回收,会强制触发耗时最长的Gen 2回收,影响程序性能。 - 对于大对象,尽量复用而不是频繁创建和销毁,避免大对象堆的频繁回收。
- 及时解除不必要的对象引用,让垃圾对象能尽早被Gen 0回收,减少晋升到更高代的概率。