一、内存基础:栈 vs 堆
C# 把内存分成两块核心区域,分配规则完全不同,这是理解 GC 的前提。
1. 栈内存 (Stack)
- 存放内容:值类型(int、bool、struct)、引用类型变量的引用地址
- 特点:
- 自动分配、自动释放(方法执行完立刻释放)
- 速度极快,无需 GC 管理
- 大小固定,空间小
- 生命周期:跟随方法 / 代码块,执行结束立即回收
2. 托管堆 (Heap)
- 存放内容:引用类型(string、class、数组、委托)
- 特点:
- 动态分配,空间大
- 手动不释放,由 GC(垃圾回收器)自动管理
- 生命周期:没有引用指向它时,GC 才会回收
总结
int a = 10; // 栈:方法结束自动没了
string s = "abc"; // 栈存引用地址,堆存真实数据:GC 负责回收
二、GC 常识:垃圾回收机制
GC = Garbage Collector,C# 的自动内存管家,专门清理托管堆上没用的对象。
1. GC 做什么?
- 找到堆上没有任何引用的无用对象
- 释放它们的内存
- 压缩内存(让存活对象紧凑排列,减少内存碎片)
2. GC 什么时候工作?
- 新对象分配,堆空间不足时(主动触发)
- 系统内存紧张时
- 手动调用
GC.Collect()(不推荐,会影响性能)
3. GC 关键特点
- 只清理托管资源
托管资源 = 堆上的对象(string、class 等)
- 不清理非托管资源
非托管资源 = 文件句柄、数据库连接、网络套接字、图片流、窗口句柄
→ 这些必须手动释放,GC 管不了!
- GC 回收是异步、不确定时间的
你无法控制它什么时候执行,所以非托管资源不能等 GC。
三、IDisposable 接口:手动释放非托管资源
1. 为什么需要它?
GC 不管非托管资源,如果不手动释放:
- 文件被占用无法删除
- 数据库连接耗尽
- 内存泄漏
- 程序卡顿、崩溃
解决方案:让类实现 IDisposable
2. IDisposable 只有一个方法
public interface IDisposable
{
void Dispose();
}
3. 实现模板
public class MyResource : IDisposable
{
// 标记是否已经释放
private bool _disposed = false;
// 公共方法:外部调用释放
public void Dispose()
{
Dispose(true);
// 告诉 GC:不用调用析构函数了,我已经手动释放完了
GC.SuppressFinalize(this);
}
// 核心释放逻辑
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
// 释放 托管资源
}
// 释放 非托管资源(文件、数据库连接、流等)
// 例:file.Close(); dbConnection.Close();
_disposed = true;
}
// 析构函数:防止用户忘记调用 Dispose,GC 时兜底
~MyResource()
{
Dispose(false);
}
}
四、using 模式:优雅的自动释放
using 是 C# 给 IDisposable 准备的语法糖,自动调用 Dispose (),无论代码是否正常执行、是否报错。
1. 核心作用
- 包裹实现了 IDisposable 的对象
- 代码块结束 → 自动释放资源
- 异常也能保证释放,绝对安全
2. 两种写法
写法 1:推荐(简洁)
using (var stream = new FileStream("test.txt", FileMode.Create))
{
// 使用流
stream.WriteByte(1);
}
// 离开大括号:自动调用 stream.Dispose()
写法 2:多个资源(C# 8.0+)
using var stream = new FileStream("test.txt", FileMode.Create);
using var reader = new StreamReader(stream);
// 方法结束时自动释放
3. 本质原理
using 等价于 try-finally:
FileStream stream = null;
try
{
stream = new FileStream("test.txt", FileMode.Create);
}
finally
{
stream?.Dispose();
}
五、四者关系总结
- 内存基础:栈自动释放,堆靠 GC
- GC:只回收托管堆,不管非托管资源
- IDisposable:定义手动释放非托管资源的规范
- using:简化
IDisposable,自动调用 Dispose(),安全不遗漏
六、开发实践
- 只要用到文件、数据库、流、Socket、图片,一律用
using
- 自己写的类包含非托管资源 → 实现
IDisposable
- 不要手动调用 GC.Collect ()
- using 优先于手动 Dispose ()
总结
- 栈自动释放,堆由 GC 管理,GC 不处理非托管资源
- IDisposable 是手动释放非托管资源的标准接口
- using 是自动调用 Dispose 的语法糖,安全、简洁、你就用吧
- 文件 / 流 / 数据库连接 → 全部套
using
来源:https://www.cnblogs.com/chuansheng/p/19909164 |