====== C# 中的 GC 回收基础与注意事项 ====== ===== 1. 什么是 GC(Garbage Collection) ===== **GC(垃圾回收)** 是 .NET CLR 提供的一种自动内存管理机制,用来回收**不再被使用的托管对象**。 > 简单理解: > 当一个对象**再也找不到引用它的路径**时,GC 就会回收它。 特点: * 自动执行,无需手动 free * 只回收托管内存(Managed Heap) * 并非实时执行 ----- ===== 2. GC 如何判断对象是否需要回收 ===== GC 的核心判断标准是: > **对象是否还能从 GC Root 被“访问到(Reachable)”** 如果不能访问到,则对象被认为是垃圾。 ----- ===== 3. 什么是 GC Root ===== GC Root 是 GC 查找引用的起点,只要对象能从这些地方被引用,就不会被回收。 常见 GC Root 包括: * 当前方法中的局部变量(栈上的引用) * 静态变量(static 字段) * 当前线程对象 * 全局单例 * **事件的发布者(非常重要)** * 非托管代码(如 Rhino / COM / C++)持有的对象 ----- ===== 4. GC 回收的基本流程 ===== - 从所有 GC Root 开始遍历 - 标记所有可达对象 - 未被标记的对象视为垃圾 - 释放其占用的内存 ----- ===== 5. GC 的分代机制 ===== 为了提高性能,.NET 使用**分代回收(Generational GC)**。 ^ 代(Generation) ^ 说明 ^ | Gen 0 | 新创建的对象 | | Gen 1 | 存活过一次回收 | | Gen 2 | 长时间存活的对象 | 说明: * 大部分对象会在 Gen 0 被回收 * Gen 2 回收成本最高 * 长期被引用的 UI / Panel 对象通常会进入 Gen 2 ----- ===== 6. GC 不会回收哪些东西 ===== GC **不会回收**以下内容: * 正在被引用的对象 * 静态变量引用的对象 * 被事件订阅的对象 * 非托管资源(如文件句柄、GDI、显存) ----- ===== 7. 事件与 GC(最常见的内存泄漏来源) ===== 在 C# 中: > **事件 = 发布者持有订阅者的强引用** 示例: class Publisher { public static event Action OnEvent; } class Subscriber { public Subscriber() { Publisher.OnEvent += Handle; } void Handle() { } } 问题: * Publisher 是 static(GC Root) * Subscriber 被事件引用 * 即使外部不再使用 Subscriber,GC 也无法回收 解决方法: * 在对象生命周期结束时必须使用 -= 取消订阅 ----- ===== 8. UI / Panel 中的 GC 注意事项 ===== UI 对象通常具有: * 生命周期短 * 创建 / 销毁频繁 * 但容易被全局对象引用 常见问题来源: * static 事件 * 文档级事件(如 RhinoDoc) * 单例管理器 推荐做法: * PanelShown 时订阅事件 * PanelHidden / PanelClosing 时取消订阅 ----- ===== 9. IDisposable 与 GC 的关系 ===== GC **不会自动释放非托管资源**。 对于以下资源: * 文件 * Socket * GDI / 显卡资源 * Rhino / COM 对象 必须实现 IDisposable: class MyResource : IDisposable { public void Dispose() { // 手动释放非托管资源 } } 使用方式: using (var res = new MyResource()) { // 使用资源 } ----- ===== 10. Finalizer(析构函数) ===== Finalizer(~ClassName): * 由 GC 在不确定时间调用 * 性能成本高 * 不保证立即执行 不推荐滥用: ~MyClass() { // 不可靠,且影响性能 } 推荐使用: * IDisposable + using * 明确的资源释放时机 ----- ===== 11. GC.Collect() 的正确认知 ===== GC.Collect(): * 强制触发 GC * 会导致性能下降 * 破坏 GC 的优化策略 结论: * ❌ 不推荐在业务代码中调用 * ✅ 只在调试或内存分析场景下使用 ----- ===== 12. 常见 GC 误区 ===== * C# 有 GC 就不会内存泄漏(错误) * 对象设为 null 就一定会回收(错误) * UI 关闭等于对象销毁(错误) * GC 会处理事件引用(错误) ----- ===== 13. 实践总结 ===== **什么时候必须关注 GC:** * 插件开发(Rhino / Revit / Unity) * 长时间运行的程序 * 使用 static / 单例 / 全局事件 * UI 面板频繁创建和销毁 **核心原则一句话:** > **让对象在不需要时,从所有 GC Root 中“断开引用”**