C# GC回收的方法实现

 更新时间:2025年11月27日 10:57:26   作者:她说彩礼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)”?

  • 全局/静态变量
  • 方法中的局部变量(包括参数)
  • 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,避免全堆扫描,提升性能。

回收过程简述:

  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 对性能的影响 & 优化建议

可能的问题:

  • 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# 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回收内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • UGUI绘制多点连续的平滑曲线

    UGUI绘制多点连续的平滑曲线

    这篇文章主要为大家详细介绍了UGUI绘制多点连续的平滑曲线,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-12-12
  • C#(WinForm) ComboBox和ListBox添加项及设置默认选择项

    C#(WinForm) ComboBox和ListBox添加项及设置默认选择项

    这篇文章主要介绍了C#(WinForm) ComboBox和ListBox添加项及设置默认选择项的的相关资料,需要的朋友可以参考下
    2014-07-07
  • C#实现ASCII和字符串相互转换的代码示例

    C#实现ASCII和字符串相互转换的代码示例

    在C#编程语言中,ASCII码是一种广泛使用的字符编码标准,它将128个不同的字符与7位的二进制数字对应起来,在处理文本时,我们有时需要将ASCII码与字符串进行相互转换,以下是如何在C#中实现这一操作的详细说明,需要的朋友可以参考下
    2026-01-01
  • C# WinForm状态栏实时显示当前时间(窗体状态栏StatusStrip示例)

    C# WinForm状态栏实时显示当前时间(窗体状态栏StatusStrip示例)

    这篇文章主要介绍了C# WinForm状态栏实时显示当前时间(窗体状态栏StatusStrip示例),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • C#项目找不到命名空间问题的排查记录与解决方案

    C#项目找不到命名空间问题的排查记录与解决方案

    代码从 Git 仓库克隆下来后,NuGet 显示包已安装,但编译时大量 CS0246,PdfSharp 全部找不到,本文完整复盘问题现象、原因分析与最终解决方案,供以后自己和同事快速定位,需要的朋友可以参考下
    2026-01-01
  • C#(.net)中按字节数截取字符串最后出现乱码问题的解决

    C#(.net)中按字节数截取字符串最后出现乱码问题的解决

    这篇文章主要给大家介绍了关于C#(.net)中按字节数截取字符串最后出现乱码问题的解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-06-06
  • c# list部分操作实现代码

    c# list部分操作实现代码

    这篇文章主要介绍了c# list部分操作,需要的朋友可以参考下
    2013-09-09
  • C#使用itextsharp生成PDF文件的实现代码

    C#使用itextsharp生成PDF文件的实现代码

    以下是对在C#中使用itextsharp生成PDF文件的实现代码进行了详细分析介绍,需要的朋友可以过来参考下
    2013-07-07
  • c# SqlFunc.IF使用方法小结

    c# SqlFunc.IF使用方法小结

    SqlFunc.IF是EF+库中的一个功能,允许你在数据库查询中执行条件逻辑,类似于SQL中的IF语句,本文就来详细的介绍一下c# SqlFunc.IF使用方法,感兴趣的可以了解一下
    2025-11-11
  • CAD2008+VS2008开发ObjectARX加载失败问题(推荐)

    CAD2008+VS2008开发ObjectARX加载失败问题(推荐)

    这篇文章主要介绍了CAD2008+VS2008开发ObjectARX加载失败问题,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-04-04

最新评论