一、内存泄漏定义
在C#中,内存泄漏指程序因设计错误导致无法释放不再使用的内存,使对象长期驻留内存,最终可能引发性能下降或内存溢出(Out of Memory)。与C/C++不同,C#的泄漏通常由错误的对象引用管理引起,而非直接的内存分配未释放。
二、内存泄漏原理
虽然C#的垃圾回收器(GC)会自动管理内存,但以下情况会阻止回收:
- 事件未取消订阅:事件发布者持有订阅者的强引用,导致订阅者无法被GC回收
// 示例:事件订阅后未取消 publisher.Event += subscriber.Handler; - 静态字段引用:静态对象生命周期与程序一致,引用的对象无法被回收
- 未释放非托管资源:新建的继承于IDisposable的对象在使用后未调用
Dispose()class A : IDisposable { public void Dispose(){ } } void Test() { //非托管类型被实例化后若不主动调用Dispose则会常驻在内存中 A a = new(); } - 循环引用:对象间相互引用,但未被外部使用(GC可处理部分情况)
class A { public B b; public A() { b = new(); } } class B { public A a; } A a = new(); //A→B→A 循环引用 a.b.a = a; - 长生命周期对象持有短生命周期对象:如全局缓存持有临时对象
public static List<object> caches = new (); public static void Test() { //该临时变量长期存在于List中,无法被GC回收 var temp = new object(); caches.Add(temp); }
三、解决方案
1. 事件处理
- 显式注销订阅:
publisher.Event -= subscriber.Handler; // 移除事件 - 使用弱事件模式(如
WeakEventManager)
2. 资源释放
- 实现
IDisposable接口:using (var resource = new FileStream(...)) { // 自动调用Dispose() } - 及时释放非托管资源
- 编写析构函数,结合释放模式,为IDisposable编写兜底机制 实现 Dispose 方法 - .NET | Microsoft Learn
public class A : IDisposable { //标记对象是否被释放 private bool disposedValue; public void Operation() { Debug.Log("Operation"); } //在开发者忘记调用Dispose时,会在GC.Collect时调用析构,以保证内存正常 ~A() { Dispose(false); } //disposing用于区分是GC回收还是开发者手动回收 protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { // TODO: 释放托管状态(托管对象) } // TODO: 释放未托管的资源(未托管的对象)并重写终结器 // TODO: 将大型字段设置为 null disposedValue = true; } } // // TODO: 仅当“Dispose(bool disposing)”拥有用于释放未托管资源的代码时才替代终结器 // ~OutOfMemoryObject() // { // // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中 // Dispose(disposing: false); // } public void Dispose() { // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中 Dispose(disposing: true); GC.SuppressFinalize(this); } }
3. 引用管理
- 避免静态字段长期引用对象:必要时重置为
null - 使用
WeakReference:允许对象被GC回收var weakRef = new WeakReference(myObject);
4. 集合清理
- 定期清理静态集合:移除不再使用的对象

1997

被折叠的 条评论
为什么被折叠?



