GC(Garbage Collector,垃圾回收器)
C# 中的 GC(Garbage Collector,垃圾回收器)是 .NET CLR(公共语言运行时)中负责 自动管理内存 的核心组件。它的目标是:
- 自动释放不再使用的对象所占的内存。
- 减少内存泄漏和悬挂指针等问题。
- 避免程序员手动释放内存,提高开发效率和安全性。
下面详细介绍 C# 中 GC 的 工作原理、代际模型、算法流程、影响因素、调优方式 等内容:
🧠 一、GC 的核心理念
GC 的核心是 “标记-清除” 思想:
- 标记(Mark):标记出仍然被引用的对象。
- 清除(Sweep):释放未被标记的(即不可达的)对象。
- 压缩(Compact):整理内存空间,消除碎片。
🧬 二、代际垃圾回收(Generational GC)
为了提高性能,.NET 的 GC 实现了 代际回收机制:
| 代(Generation) | 说明 | 特点 |
|---|---|---|
| Gen 0(第0代) | 新生代,生命周期短 | 回收频繁、开销小 |
| Gen 1(第1代) | 临时保留代 | Gen0晋升未被清理对象 |
| Gen 2(第2代) | 老年代,生命周期长 | 对象长期存在,回收代价高 |
| LOH(大对象堆) | >85KB的对象 | 直接分配到 Gen2,不会压缩 |
GC晋升规则:
- 对象初始分配在 Gen0。
- 存活的 Gen0 对象晋升到 Gen1,Gen1 → Gen2。
- LOH 不会随 Gen0 回收,通常由完整 GC(Full GC)处理。
⚙️ 三、GC 的工作流程(简化版)
1. 内存分配
- 对象通过
new操作符在托管堆上分配。 - CLR 为 Gen0 分配一个连续内存区域(称为堆段)。
2. 触发时机
GC 并不是每次分配都触发,只有以下情况才触发:
- Gen0 内存不足。
- 显式调用
GC.Collect()。 - 系统内存压力较大(如物理内存紧张)。
- 程序空闲时后台 GC 运行。
3. 回收过程
- 暂停所有托管线程(称为 Stop-the-World)。
- 标记所有可达对象(从 GC Root 开始,如静态变量、栈变量等)。
- 清除未被标记的对象。
- 压缩堆(仅部分回收模式执行压缩)。
🧰 四、GC 的算法和策略
.NET CLR 的 GC 是一种 非分代并发压缩标记-清除 GC,具体细节:
1. GC Root 搜索
GC 从 Root 开始搜索引用路径(称为可达性分析),如:
- 静态字段
- 当前线程的堆栈变量
- CPU 寄存器中的变量
- GCHandle、FinalizerQueue 中的对象
2. 回收策略
| 类型 | 特点 |
|---|---|
| Workstation GC | 面向桌面应用,低延迟,适合交互式程序 |
| Server GC | 多线程并行回收,适合高吞吐量场景,如 ASP.NET |
| Background GC | Gen2 回收在后台线程进行,不阻塞 Gen0/Gen1 回收 |
| Concurrent GC | 支持并发标记阶段,减少暂停时间 |
📦 五、GC 的对象生命周期管理
1. Finalizer(终结器)
- C# 中用
~ClassName()定义终结器。 - 当对象被标记为回收,但实现了终结器,会被移动到 Finalization Queue。
- 回收会延迟到下一次 GC,且不能确定执行时间。
⚠️ Finalizer 会增加对象生命周期,导致 Gen2 压力增加。建议少用。
🧠 六、常见问题与误区
1. Dispose() 和 GC 的关系?
Dispose()是手动释放资源(如文件、数据库连接)的约定。- 和 GC 自动管理的托管内存无关。
- 实现
IDisposable接口通常配合using使用。
2. GC.Collect() 能强制回收内存吗?
- 能请求回收,但不推荐频繁手动调用,会破坏 GC 的优化机制。
🔧 七、调优与监控 GC
1. 如何查看 GC 情况?
使用以下工具可分析 GC 行为:
- Visual Studio Diagnostic Tools
- PerfView
- dotnet-counters / dotnet-trace
- Windows Performance Monitor
2. 配置 GC 行为
在 .csproj 或 runtimeconfig.json 中设置:
<ServerGarbageCollection>true</ServerGarbageCollection>
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
<RetainVM>true</RetainVM>
✅ 总结:GC 的优势与注意事项
| 优点 | 注意事项 |
|---|---|
| 自动内存管理,提升开发效率 | 不要过度依赖终结器 |
| 防止内存泄漏和悬挂指针 | 控制大对象使用(LOH) |
| 动态优化回收策略 | 不频繁调用 GC.Collect() |
总结
C# 中的 GC(垃圾回收器)是 .NET 用来自动管理内存的机制。它会在后台自动查找那些不再使用的对象,并释放它们占用的内存,从而避免内存泄漏或手动释放的麻烦。
它采用 代际回收机制,把对象分为三代:第0代是新对象,回收频繁;第2代是老对象,生命周期长、回收少。
GC 是“标记-清除-压缩”算法的一种实现,默认使用后台并发 GC,适合高吞吐或实时场景。实际开发中,大对象会进入特殊的堆区(LOH),这类对象不易被频繁回收,我们会尽量避免频繁创建大对象。
🧠 一、 GC 工作原理总结(准确 + 通俗)
只需要记住这一张图概念:
new 对象 → Gen0 → Gen1 → Gen2 →(偶尔)GC 回收
↑ ↑ ↑
回收频繁 回收较少 回收最少(代价高)
✅ 1. 为什么需要 GC?
.NET 中对象是通过 new 分配在“托管堆”上的,而不像 C/C++ 需要手动释放内存。
GC 的职责是:
- 自动回收不再使用的对象(内存)
- 避免内存泄漏、野指针等 bug
✅ 2. GC 是怎么判断哪些对象该被回收?
GC 会从一组“GC Root”开始(包括静态变量、栈上的引用、线程上下文等),沿引用关系找出所有“可达对象”。
- 可达对象:还被引用 → 保留
- 不可达对象:没人引用了 → 可以安全释放
👉 所以核心是:不是“用了多久”,而是“还能不能被访问”
🚀 二、GC 的工作机制简要流程
✅ 3. 分代(Generational GC)——性能优化关键
对象生命周期分为三代:
| 代 | 含义 | 特性 |
|---|---|---|
| Gen0 | 新建对象(大多数) | 回收最快,最频繁 |
| Gen1 | 从 Gen0 晋升而来 | 缓冲层 |
| Gen2 | 活得最久的对象 | 回收最少,代价最大 |
💡 统计显示:大多数对象“很快就死”,所以优先高频回收 Gen0,能极大提升效率。
例:你 new 了 100 万个字符串,其中只用了 10 个,其它都在 Gen0 被很快释放。
✅ 4. 回收过程(Stop-the-World)
GC 主要过程如下:
- 暂停所有托管线程(Stop-the-World)
- 从 GC Root 开始,标记所有“还活着”的对象
- 清除没被标记的对象(没人用了)
- 必要时压缩内存(让对象搬到连续空间)
🧰 三、掌握这些判断点
| 关键此 | 说明 | 水平判断 |
|---|---|---|
| GC 自动管理内存 | 必须知道 | 初级基础 |
| GC 分代模型 | 正确提到 Gen0/Gen1/Gen2 | 中级 |
| 可达性分析 / GC Root | 知道 GC 如何判断要不要回收对象 | 中级偏上 |
| Stop-the-World、压缩堆 | 懂 GC 会暂停线程、压缩碎片 | 中高级 |
| LOH、大对象堆 | 知道 85K 大对象怎么处理 | 高级偏上 |
GC.Collect()、Dispose() 区分清楚 |
正确认识 GC 与资源释放职责 | 中级必要掌握 |
✅ 简明口诀
C# GC 是托管内存的“自动清道夫”
回收策略按对象年龄分代(Gen0 快速、Gen2 慢)
回收逻辑是:从 GC Root 出发,活着的保留,死掉的清理
堆有碎片就压缩,线程会暂停(Stop-the-World)

浙公网安备 33010602011771号