C#教程

关注公众号 jb51net

关闭
首页 > 软件编程 > C#教程 > C# GC回收

C# GC回收的方法实现

作者:她说彩礼65万

本文主要介绍了C# GC回收的方法实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

介绍 C# 中的 垃圾回收(Garbage Collection, GC) 机制。

🌟 一、什么是 GC?

GC(Garbage Collector,垃圾回收器) 是 .NET 运行时(CLR)自动管理内存的核心组件。

它的作用是:

自动找出不再被使用的对象(“垃圾”),并释放它们占用的内存,防止内存泄漏。

你不需要像 C/C++ 那样手动 deletefree,C# 的 GC 会帮你搞定!

🧱 二、GC 管理的是哪部分内存?

在 C# 中,内存主要分为两块:

内存区域存放内容是否由 GC 管理
托管堆(Managed Heap)引用类型对象(如 class 实例)✅ 是
栈(Stack)值类型(如 int, struct)、方法局部变量、引用类型的引用(指针)❌ 否(随方法结束自动释放)

🔸 注意:string、数组、自定义 class 都分配在托管堆上,由 GC 负责回收。

🔁 三、GC 什么时候触发?

GC 不是实时运行的,而是在满足以下条件之一时自动触发

  1. 托管堆内存不足(分配新对象时发现空间不够)
  2. 系统内存压力大(Windows 通知 .NET 内存紧张)
  3. 手动调用 GC.Collect()(不推荐!除非特殊场景)

⚠️ 开发者通常无法精确控制 GC 何时发生,这是设计上的有意为之——让开发者专注业务逻辑。

🗂️ 四、GC 如何判断一个对象是“垃圾”?

核心原则:如果一个对象无法从“根(Root)”访问到,它就是垃圾。

什么是“根(Root)”?

示例:

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,避免全堆扫描,提升性能。

回收过程简述:

  1. GC 暂停所有线程(“Stop-The-World”)
  2. 从“根”出发,标记所有可达对象
  3. 清除未标记对象(垃圾)
  4. 压缩内存:把存活对象往堆的一端移动,消除碎片
  5. 晋升: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 对性能的影响 & 优化建议

可能的问题:

优化建议:

场景建议
频繁创建小对象✔️ 重用对象(对象池 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# GCC++ 手动管理
内存安全✅ 不会野指针、重复释放❌ 容易出错
开发效率✅ 高❌ 低
性能可控性❌ 较低(GC 不可预测)✅ 高
内存碎片✅ GC 会压缩堆❌ 需手动管理

💡 C# 的设计理念:用少量性能代价,换取极高的开发安全性和效率

✅ 总结:关键要点

  1. GC 自动回收托管堆上的无用对象,开发者无需手动释放。
  2. 分代回收(Gen 0/1/2) 是核心优化策略,基于“多数对象短命”假设。
  3. GC 不处理非托管资源 → 必须用 IDisposable + using。
  4. 避免频繁分配临时对象,尤其在循环或高频方法中。
  5. 不要随意调用 GC.Collect() —— 通常适得其反。
  6. 大对象(≥85KB)进入 LOH,不压缩,需特别注意。

如果你正在开发高性能应用(如游戏、实时系统),理解 GC 行为至关重要;如果是普通业务系统,只需记住:少 new 临时对象,及时 Dispose 资源,就能避开 90% 的问题。

到此这篇关于C# GC回收的方法实现的文章就介绍到这了,更多相关C# GC回收内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文