本文主要是介绍C# GC回收的方法实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
《C#GC回收的方法实现》本文主要介绍了C#GC回收的方法实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...
介绍 C# 中的 垃圾回收(Garbage Collection, GC) 机制。
一、什么是 GC?
GC(Garbage Collector,垃圾回收器) 是 .NET 运行时(CLR)自动管理内存的核心组件。
它的作用是:
自动找出不再被使用的对象(“垃圾”),并释放它们占用的内存,防止内存泄漏。
你不需要像 C/C++ 那样手动 delete 或 free,C# 的 GC 会帮你搞定!
二、GC 管理的是哪部分内存?
在 C# 中,内存主要分为两块:
| 内存区域 | 存放内容 | 是否由 GC 管理 |
|---|---|---|
| 托管堆(Managed Heap) | 引用类型对象(如 class 实例) | ✅ 是 |
| 栈(Stack) | 值类型(如 int, struct)、方法局部变量、引用类型的引用(指针) | ❌ 否(随方法结束自动释放) |
注意:string、数组、自定义 class 都分配在托管堆上,由 GC 负责回收。
三、GC 什么时候触发?
GC 不是实时运行的,而是在满足以下条件之一时自动触发:
⚠️ 开发者通常无法精确控制 GC 何时发生,这是设计上的有意为之——让开发者专注业务逻辑。
️ 四、GC 如何判断一个对象是“垃圾”?
核心原则:如果一个对象无法从“根(Root)”访问到,它就是垃圾。
什么是“根(Root)”?
- 全局/静态变量
- 方法中的局部变量(包括参数)
- CPU 寄存器中的引用
- 本地方法(Native code)持有的引用
示例:
void MyMethod()
{
var person = new Person(); // person 是局部变量 → 根
person.Name = "Alice";
} // 方法结束,person 超出作用域 → 不再是根
→ 此时 Person 对象不可达,下次 GC 时会被回收。
五、GC 的核心机制:分代回收(Generational GC)
.NET 的 GC 采用 “分代回收” 策略,把对象按“年龄”分成三代:
| 代(Generation) | 特点 | 回收频率 |
|---|---|---|
| Gen 0 | 新创建的对象 | ⏱️ 最频繁(毫秒级) |
| Gen 1 | 活过一次 Gen 0 回收的对象 | 中等 |
| Gen 2 | 老对象(活过 Gen 1) | 最少(可能几秒甚至几分钟一次) |
为什么分代?
经验规律:大多数对象“朝生暮死”(比如临时变量)。
所以优先快速回收 Gen 0,避免全堆扫描,提升性能。
回收过程简述:
- GC 暂停所有线程(“Stop-The-World”)
- 从“根”出发,标记所有可达对象
- 清除未标记对象(垃圾)
- 压缩内存:把存活对象往编程堆的一端移动,消除碎片
- 晋升:Gen 0 存活对象 → Gen 1;Gen 1 存活 → Gen 2
Gen 2 回收也叫 Full GC,开销最大,应尽量避免频繁发生。
六、如何查看对象在哪一代?
可以使用 GC.GetGeneration(object):
var obj = new object(); Console.WriteLine(GC.GetGeneration(obj)); // 输出 0 GC.Collect(); // 强制回收 Gen 0 Console.WriteLine(GC.GetGeneration(obj)); // 如果 obj 还被引用,输出 1
️ 七、特殊对象:需要“清理”的资源
GC 只负责内存回收,但有些对象持有非托管资源(如文件句柄、数据库连接、网络 socket),这些资源不会自动释放!
解决方案:实现IDisposable+Dispose模式
public class MyFile : IDisposable
{
private FileStream _file;
public MyFile(string path)
{
_file = File.OpenRead(path);
}
public void Dispose()
{
_file?.Dispose(); // 立即释放非托管资源
GC.SuppressFinalize(this); // 告诉 GC:不用调用析构函数了
}
~MyFile() // 析构函数(Finalizer)— 最后的保险
{
Dispose();
}
}
// 使用方式(推荐 using)
using (var file = new MyFile("data.txt"))
{
// 使用文件
} // 自动调用 Dispose()
✅ 最佳实践:对持有非托管资源的类,务必实现 IDisposable,并用 using 语句确保及时释放。
八、GC 对性能的影响 &a编程mp; 优化建议
可能的问题:
- Stop-The-World:GC 期间所有线程暂停,可能导致 UI 卡顿
- 频繁分配/回收:大量临时对象 → 频繁 Gen 0 GC → CPU 浪费
- 大对象堆(LOH):≥85,000 字节的对象分配在 LOH,不会压缩,易产生内存碎片
优化建议:
| 场景 | 建议 |
|---|---|
| 频繁创建小对象 | ✔️ 重用对象(对象池 ObjectPool<T>) |
| 大数组/缓冲区 | ✔️ 使用 ArrayPool<T>.Shared |
| 长时间持有对象 | ✔️ 避免无意中延长对象生命周期(如事件订阅未取消) |
| 高性能场景 | ✔️ 减少分配(如用 Span<T>、stackalloc) |
| 监控 GC | ✔️ 使用 PerfView、dotMemory 或 GC.CollectionCount |
// 查看各代 GC 次数
Console.WriteLine($"Gen 0: {GC.CollectionCount(0)}");
Console.WriteLine($"Gen 1: {GC.CollectionCount(1)}");
Console.WriteLine($"Gen 2: {GC.CollectionCount(2)}");
九、GC vs 手动内存管理(C++)
| 特性 | C# GC | C++ 手动管理 |
|---|---|---|
| 内存安全 | ✅ 不会野指针、重复释放 | ❌ 容易出错 |
| 开发效率 | ✅ 高 | ❌ 低 |
| 性能可控性 | ❌ 较低(GC 不可预测) | ✅ 高 |
| 内存碎片 | ✅ GC 会压缩堆 | ❌ 需手动管理 |
C# 的设计理念:用少量性能代价,换取极高的开发安全性和效率。
✅ 总结:关键要点
- GC 自动回收托管堆上的无用对象,开发者无需手动释放。
- 分代回收(Gen 0http://www.chinasem.cn/1/2) 是核心优化策略,javascript基于“多数对象短命”假设。
- GC 不处理非托管资源 → 必须用 IDisposable + using。
- 避免频繁分配临时对象,尤其在循环或高频方法中。
- 不要随意调用 GC.Collect() —— 通常适得其反。
- 大对象(≥85KB)进入 LOH,不压缩,需特别注意。
如果你正在开发高性能应用(如游戏、实时系统),理解 GC 行为至关重要;如果是普通业务系统,只需记住:少 new 临时对象,及时 Dispose 资源,就能避开 90% 的问题。
到此这篇关于C# GC回收的方法实现的文章就介绍到这了,更多相关C# GC回收内容请搜索编程tmNiKSChina编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持China编程(www.chinasem.cn)!
这篇关于C# GC回收的方法实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!