VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • C#中如保进行内存管理——避免内存泄漏

第十部分:性能优化与内存管理
实例95:内存管理——避免内存泄漏
实例介绍
之前做电商订单系统时,发现系统运行一周后内存占用从2GB涨到10GB,响应时间从100ms涨到500ms,最终导致系统崩溃。通过内存分析工具排查,发现是数据库连接未释放、事件订阅未取消、静态集合未清理导致的内存泄漏。修复后,系统运行一个月内存稳定在2.5GB左右,响应时间保持在100ms以内。这节就带你从零学习C#内存管理,识别常见的内存泄漏场景,掌握内存分析工具的使用,实现内存泄漏的预防和修复。
需求分析
内存管理要解决“内存泄漏识别、内存泄漏修复、内存优化、性能提升”的问题,具体需求如下:
1.内存泄漏识别:掌握常见的内存泄漏场景,如未释放的非托管资源、事件订阅未取消、静态集合未清理;
2.内存泄漏修复:学习如何正确释放资源、取消事件订阅、清理静态集合;
3.内存优化:掌握内存优化技巧,如对象池、弱引用、值类型优化;
4.性能提升:通过内存管理提升系统性能,减少GC压力;
5.工具使用:掌握内存分析工具的使用,如Visual Studio内存诊断、dotMemory;
代码实现
前置条件:.NET 8 SDK;Visual Studio 2022;dotMemory(可选);
场景1:非托管资源泄漏——数据库连接未释放
未正确释放数据库连接导致的内存泄漏,使用using语句或IDisposable接口释放资源。
步骤1:错误代码(MemoryLeakExample.cs)
csharp

	using System.Data.SqlClient;
	
	namespace MemoryManagementDemo;
	
	public class DatabaseService
	{
	// 错误:未释放数据库连接,导致连接池耗尽,内存泄漏
	public void GetOrders()
	{
	var connection = new SqlConnection("Server=.;Database=OrderDb;Integrated Security=True;");
	connection.Open();
	
	var command = new SqlCommand("SELECT * FROM Orders", connection);
	var reader = command.ExecuteReader();
	
	// 处理数据
	while (reader.Read())
	{
	Console.WriteLine(reader["OrderId"]);
	}
	
	// 错误:未关闭连接、释放资源
	// connection.Close();
	// connection.Dispose();
	// reader.Dispose();
	// command.Dispose();
	}
	}

步骤2:修复代码(使用using语句)
csharp

	using System.Data.SqlClient;
	
	namespace MemoryManagementDemo;
	
	public class DatabaseService
	{
	// 正确:使用using语句自动释放资源
	public void GetOrders()
	{
	// using语句会自动调用Dispose方法释放资源
	using var connection = new SqlConnection("Server=.;Database=OrderDb;Integrated Security=True;");
	connection.Open();
	
	using var command = new SqlCommand("SELECT * FROM Orders", connection);
	using var reader = command.ExecuteReader();
	
	while (reader.Read())
	{
	Console.WriteLine(reader["OrderId"]);
	}
	
	// 无需手动调用Close和Dispose,using语句会自动处理
	}
	}

场景2:事件订阅泄漏——未取消事件订阅
事件订阅未取消导致的内存泄漏,使用WeakEventManager或手动取消订阅。
步骤1:错误代码(EventLeakExample.cs)
csharp

	using System;
	
	namespace MemoryManagementDemo;
	
	public class OrderService
	{
	// 定义事件
	public event EventHandler<OrderCreatedEventArgs> OrderCreated;
	
	public void CreateOrder(Order order)
	{
	// 处理订单创建逻辑
	OnOrderCreated(new OrderCreatedEventArgs(order));
	}
	
	protected virtual void OnOrderCreated(OrderCreatedEventArgs e)
	{
	OrderCreated?.Invoke(this, e);
	}
	}
	
	public class OrderCreatedEventArgs : EventArgs
	{
	public Order Order { get; }
	
	public OrderCreatedEventArgs(Order order)
	{
	Order = order;
	}
	}
	
	public class NotificationService
	{
	private readonly OrderService _orderService;
	
	public NotificationService(OrderService orderService)
	{
	_orderService = orderService;
	// 错误:订阅事件后未取消,导致NotificationService实例无法被GC回收
	_orderService.OrderCreated += OnOrderCreated;
	}
	
	private void OnOrderCreated(object? sender, OrderCreatedEventArgs e)
	{
	Console.WriteLine($"发送订单通知:{e.Order.OrderId}");
	}
	
	// 错误:未提供取消订阅的方法
	// public void Unsubscribe()
	// {
	// _orderService.OrderCreated -= OnOrderCreated;
	// }
	}

步骤2:修复代码(手动取消订阅)
csharp

	using System;
	
	namespace MemoryManagementDemo;
	
	public class NotificationService : IDisposable
	{
	private readonly OrderService _orderService;
	private bool _disposed = false;
	
	public NotificationService(OrderService orderService)
	{
	_orderService = orderService;
	_orderService.OrderCreated += OnOrderCreated;
	}
	
	private void OnOrderCreated(object? sender, OrderCreatedEventArgs e)
	{
	Console.WriteLine($"发送订单通知:{e.Order.OrderId}");
	}
	
	// 提供取消订阅的方法
	public void Unsubscribe()
	{
	_orderService.OrderCreated -= OnOrderCreated;
	}
	
	// 实现IDisposable接口,在Dispose时取消订阅
	public void Dispose()
	{
	Dispose(true);
	GC.SuppressFinalize(this);
	}
	
	protected virtual void Dispose(bool disposing)
	{
	if (_disposed) return;
	
	if (disposing)
	{
	// 取消事件订阅
	_orderService.OrderCreated -= OnOrderCreated;
	}
	
	_disposed = true;
	}
	
	~NotificationService()
	{
	Dispose(false);
	}
	}

步骤3:修复代码(使用WeakEventManager)
csharp
using System;
using System.Windows; // 需要引用WindowsBase.dll

namespace MemoryManagementDemo;

public class NotificationService
{
private readonly OrderService _orderService;

public NotificationService(OrderService orderService)
{
_orderService = orderService;
// 正确:使用WeakEventManager订阅事件,避免内存泄漏
WeakEventManager<OrderService, OrderCreatedEventArgs>.AddHandler(_orderService, nameof(OrderService.OrderCreated), OnOrderCreated);
}

private void OnOrderCreated(object? sender, OrderCreatedEventArgs e)
{
Console.WriteLine($"发送订单通知:{e.Order.OrderId}");
}
}

场景3:静态集合泄漏——未清理静态集合
静态集合未清理导致的内存泄漏,使用弱引用或定期清理静态集合。
步骤1:错误代码(StaticCollectionLeakExample.cs)
csharp

	using System.Collections.Generic;
	
	namespace MemoryManagementDemo;
	
	public class CacheService
	{
	// 错误:静态集合持有对象引用,导致对象无法被GC回收,内存泄漏
	private static readonly List<Order> _orderCache = new List<Order>();
	
	public void AddToCache(Order order)
	{
	_orderCache.Add(order);
	Console.WriteLine($"添加到缓存:{order.OrderId},缓存大小:{_orderCache.Count}");
	}
	
	// 错误:未提供清理缓存的方法
	// public void ClearCache()
	// {
	// _orderCache.Clear();
	// }
	}

步骤2:修复代码(使用WeakReference)
csharp

	using System.Collections.Generic;
	using System;
	
	namespace MemoryManagementDemo;
	
	public class CacheService
	{
	// 正确:使用WeakReference持有对象引用,允许GC回收对象
	private static readonly List<WeakReference<Order>> _orderCache = new List<WeakReference<Order>>();
	
	public void AddToCache(Order order)
	{
	_orderCache.Add(new WeakReference<Order>(order));
	Console.WriteLine($"添加到缓存:{order.OrderId},缓存大小:{_orderCache.Count}");
	}
	
	public Order? GetFromCache(string orderId)
	{
	// 清理已被GC回收的对象
	_orderCache.RemoveAll(r => !r.TryGetTarget(out _));
	
	foreach (var weakRef in _orderCache)
	{
	if (weakRef.TryGetTarget(out var order) && order.OrderId == orderId)
	{
	return order;
	}
	}
	
	return null;
	}
	}

场景4:内存分析工具使用——Visual Studio内存诊断
使用Visual Studio内存诊断工具识别内存泄漏。
步骤1:启动内存诊断
1.在Visual Studio中按F5启动调试;
2.执行可能导致内存泄漏的操作(如多次调用GetOrders方法);
3.点击“调试” → “性能探查器” → 选择“内存使用情况” → 点击“开始”;
4.执行操作后,点击“获取快照”;
5.对比多个快照,查看对象数量变化,识别内存泄漏;
步骤2:分析内存泄漏
查看“对象类型”列表,找到数量异常增长的类型(如SqlConnection、NotificationService);
右键点击类型 → “显示在对象图中”,查看对象的引用链;
找到导致对象无法被GC回收的引用(如静态集合、未取消的事件订阅);
逐行讲解
场景1:非托管资源泄漏核心代码
1.using语句:using语句会自动调用对象的Dispose方法,释放非托管资源;
2.IDisposable接口:SqlConnection、SqlCommand、SqlDataReader都实现了IDisposable接口,需要调用Dispose方法释放资源;
3.连接池:未释放的数据库连接会占用连接池,导致连接池耗尽,系统无法创建新连接;
场景2:事件订阅泄漏核心代码
1.事件订阅:当对象订阅另一个对象的事件时,发布者会持有订阅者的强引用,导致订阅者无法被GC回收;
2.IDisposable接口:在Dispose方法中取消事件订阅,释放引用;
3.WeakEventManager:使用弱引用订阅事件,允许GC回收订阅者对象,避免内存泄漏;
场景3:静态集合泄漏核心代码
1.静态集合:静态集合持有对象的强引用,导致对象无法被GC回收;
2.WeakReference:弱引用允许GC回收对象,当对象被回收后,弱引用无法获取到对象;
3.定期清理:在访问缓存时,清理已被GC回收的对象,减少内存占用;
基础知识拓展

  1. .NET内存管理机制
    自动内存管理:.NET使用垃圾回收器(GC)自动管理内存,无需手动分配和释放内存;
    代龄机制:GC将对象分为3代(0代、1代、2代),优先回收0代对象,提高回收效率;
    大对象堆(LOH):大于85KB的对象会被分配到大对象堆,大对象堆的回收成本较高,容易产生内存碎片;
    非托管资源:如文件句柄、数据库连接、网络连接,需要手动释放,否则会导致资源泄漏;
  2. 常见内存泄漏场景
场景 原因 解决方案
未释放非托管资源 未调用Dispose方法释放资源,如数据库连接、文件流 使用using语句、实现IDisposable接口
事件订阅未取消 发布者持有订阅者的强引用,导致订阅者无法被GC回收 手动取消订阅、使用WeakEventManager
静态集合未清理 静态集合持有对象的强引用,导致对象无法被GC回收 使用WeakReference、定期清理静态集合
长生命周期对象持有短生命周期对象 长生命周期对象(如单例)持有短生命周期对象的引用,导致短生命周期对象无法被GC回收 使用弱引用、避免不必要的引用
终结器(Finalizer)滥用 未正确实现终结器,导致对象无法被GC回收 避免不必要的终结器,实现IDisposable接口
  1. 内存优化技巧
    对象池:重用对象,减少GC压力,如ObjectPool;
    值类型优化:使用值类型(struct)代替引用类型,减少堆内存分配;
    字符串优化:使用StringBuilder代替字符串拼接,减少字符串对象的创建;
    延迟加载:延迟加载对象,避免不必要的内存分配;
    内存对齐:合理安排类的字段顺序,减少内存碎片;
  2. 内存分析工具
    Visual Studio内存诊断:内置的内存分析工具,适合本地调试;
    dotMemory:JetBrains出品的专业内存分析工具,支持复杂内存泄漏分析;
    PerfView:微软出品的性能分析工具,支持内存、CPU、GC分析;
    CLR Profiler:微软出品的CLR分析工具,适合深入分析GC行为;
  3. GC回收机制
    触发条件:当内存不足、调用GC.Collect()、系统空闲时触发GC;
    回收过程:暂停所有线程 → 标记存活对象 → 压缩内存 → 恢复线程;
    回收模式:工作站模式(适合桌面应用)、服务器模式(适合服务器应用);
    总结
    内存管理的核心是避免内存泄漏、优化内存分配、减少GC压力,提高系统性能和稳定性。关键要点:
    1.非托管资源:使用using语句或IDisposable接口释放资源;
    2.事件订阅:手动取消订阅或使用WeakEventManager;
    3.静态集合:使用WeakReference或定期清理静态集合;
    4.内存分析:使用Visual Studio内存诊断、dotMemory等工具识别内存泄漏;
    5.内存优化:使用对象池、值类型优化、字符串优化等技巧减少内存分配;
    比如这个内存管理优化,修复数据库连接泄漏后,连接池使用率从100%降到10%;修复事件订阅泄漏后,NotificationService实例被GC正常回收;使用WeakReference后,静态集合的内存占用减少80%。掌握内存管理,你就能构建高性能、稳定的.NET应用!

本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49492.html


相关教程