====== 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 中“断开引用”**