编辑
2025-09-22
C#
00

目录

事件订阅未取消
应用场景
示例
静态字段引用
应用场景
示例
大对象堆(LOH)分配
应用场景
示例
ValueTask 滥用
应用场景
示例
测试内存泄露
总结

内存泄露是指在程序运行过程中,已分配的内存由于某些原因未能释放,导致随着程序的运行时间越长,占用的内存越多,最终可能导致程序运行缓慢甚至崩溃。在 C# .NET 环境中,尽管有垃圾回收(GC)机制帮助管理内存,但仍有一些情况会导致内存泄露。本文将通过示例探讨几种常见的内存泄露情况及其应用场景,并提供测试方法。

事件订阅未取消

应用场景

在 .NET 中,事件是实现对象间通信的一种机制。如果订阅者对象订阅了发布者对象的事件,但在不再需要时未取消订阅,那么发布者将持续持有订阅者的引用,导致订阅者对象无法被垃圾回收。

示例

C#
// 定义一个事件发布者类 public class EventPublisher { // 声明一个事件,使用 EventHandler<TEventArgs> 委托,这里的事件数据类型为 EventArgs public event EventHandler<EventArgs> MyEvent; // 触发事件的方法 public void RaiseEvent() { // ?.Invoke 安全地触发事件。如果 MyEvent 不为 null,则调用所有订阅者的方法 MyEvent?.Invoke(this, EventArgs.Empty); } } // 定义一个事件订阅者类 public class EventSubscriber { // 在构造函数中订阅事件 public EventSubscriber(EventPublisher publisher) { // 订阅 publisher 发布的 MyEvent 事件 publisher.MyEvent += Publisher_MyEvent; } // 事件处理方法,当事件被触发时执行 private void Publisher_MyEvent(object sender, EventArgs e) { // 输出消息表示事件已接收 Console.WriteLine("Event received."); } } // 测试代码 var publisher = new EventPublisher(); var subscriber = new EventSubscriber(publisher); publisher.RaiseEvent(); // 此时应取消订阅,否则 subscriber 无法被 GC 回收 // publisher.MyEvent -= subscriber.Publisher_MyEvent;

image.png

静态字段引用

应用场景

静态成员的生命周期与应用程序的生命周期相同。如果静态字段引用了一个大对象或集合,而这个对象或集合在整个应用程序的生命周期内都不再需要,那么这部分内存将不会被释放。

示例

C#
public class DataHolder { private static List<byte[]> largeDataList = new List<byte[]>(); public static void AddLargeObject() { largeDataList.Add(new byte[1024 * 1024]); // 添加 1MB 数据 } } // 测试代码 DataHolder.AddLargeObject(); // 即使 AddLargeObject 方法不再被调用,largeDataList 中的数据也不会被释放

大对象堆(LOH)分配

应用场景

.NET 中,大于 85KB 的对象会被分配到大对象堆(LOH)。LOH 的垃圾回收频率低于普通对象堆,频繁分配和释放大对象可能导致内存使用不断上升。

示例

C#
public class LargeObjectAllocator { public void AllocateLargeObject() { byte[] largeArray = new byte[85000]; // 分配到 LOH // 使用 largeArray } } // 测试代码 var allocator = new LargeObjectAllocator(); for (int i = 0; i < 1000; i++) { allocator.AllocateLargeObject(); } // 这将导致大量内存被分配到 LOH,可能会导致内存使用急剧上升

ValueTask 滥用

应用场景

ValueTask 是一个值类型,用于优化异步代码中的内存分配。但如果不正确使用(例如,对同一个 ValueTask 实例进行多次等待),可能导致意外的行为和内存泄露。

示例

C#
public class ValueTaskExample { public async ValueTask<int> GetValueAsync() { await Task.Delay(100); // 模拟异步操作 return 42; } public async Task BadUsage() { var valueTask = GetValueAsync(); var result1 = await valueTask; // 对同一个 ValueTask 实例进行了第二次等待,这是不被推荐的 var result2 = await valueTask; } } // 测试代码 var example = new ValueTaskExample(); await example.BadUsage();

测试内存泄露

测试内存泄露可以使用 .NET 自带的性能监视器(Performance Monitor),或者使用专业的内存分析工具,如 JetBrains dotMemory。这些工具可以帮助开发者监控应用程序的内存使用情况,识别内存泄露的源头。

总结

虽然 C# .NET 提供了垃圾回收机制,但开发者仍需注意代码中可能导致内存泄露的情况。通过理解常见的内存泄露场景并采取适当的预防措施,可以有效避免内存泄露,提高应用程序的性能和稳定性。

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!