You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

解决C# Web应用内存缓存引发的System.OutOfMemoryException问题

内存溢出异常排查与处理方案

一、问题根源分析

1. 线程启动场景的内存问题

  • 线程无限制创建:每次调用PreloadSavedCartsFor都会新建Thread实例,高并发下频繁调用会导致大量线程堆积,线程本身的栈内存、上下文资源长期占用无法回收。
  • HttpContext引用泄漏:将当前HttpContext赋值给后台线程的Current属性,线程执行完毕后未清理该引用。HttpContext包含Session、Request等大量资源,会导致GC无法正常回收这些对象,引发内存泄漏。
  • 异常吞噬隐藏问题:空catch块掩盖了线程执行过程中的所有异常,无法及时发现缓存加载失败、资源释放异常等问题,导致隐患长期积累。

2. 图片读取场景的内存问题

  • Image.FromFile的隐性内存泄漏:虽然用using释放了Image对象,但Image.FromFile会锁定文件句柄并保留内部非托管内存缓存,短时间内大量处理大尺寸图片时,非托管内存无法及时回收。
  • 内存重复占用:把原图片存入MemoryStream再转成byte[],这部分内存要等缩略图生成后才释放,高并发处理图片时会导致托管内存瞬间飙升。
  • 缓存机制失效:调用RetrieveImage时传入checkCacheExpiration=false,跳过缓存检查,每次都从文件读取原图片,重复加载相同图片会造成内存重复占用,浪费资源。

二、修复与优化方案

1. 线程启动场景修复

  • 改用线程池管理线程:用Task.Run替代手动创建线程,线程池会自动处理线程的创建与回收,避免线程堆积:
    public void PreloadSavedCartsFor(LoggedInUser loggedinUser)
    {
        var sessionId = System.Web.HttpContext.Current?.Session?.SessionID;
        Task.Run(() =>
        {
            try
            {
                var savedCarts = LoadCustomerSavedCarts(loggedinUser);
                if (!string.IsNullOrEmpty(sessionId))
                {
                    var currentSession = System.Web.HttpContext.Current?.Session;
                    if (currentSession != null && currentSession.SessionID == sessionId)
                    {
                        currentSession["SavedCarts"] = savedCarts;
                    }
                }
            }
            catch (Exception ex)
            {
                // 记录详细异常日志,包括用户ID、时间、堆栈信息
                Logger.Error($"预加载用户{loggedinUser.Id}购物车失败:{ex.Message}", ex);
            }
        });
    }
    
  • 移除跨线程HttpContext引用:避免直接传递HttpContext.Current,改用SessionID或用户标识获取数据,切断资源泄漏的源头。
  • 完善异常日志:删除空catch块,记录异常详情,便于定位线程执行中的问题。

2. 图片读取场景修复

  • 优化图片加载方式:用FileStream替代Image.FromFile,手动控制流的释放,减少非托管内存占用:
    public override Image RetrieveImage(string name, bool checkCacheExpiration = true)
    {
        lock (_Sync)
        {
            if (checkCacheExpiration ? Exists(name) : this.FileExists(name))
            {
                var path = ResolvePath(name);
                using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read))
                {
                    return Image.FromStream(fs);
                }
            }
            else
            {
                return null;
            }
        }
    }
    
  • 减少内存中间转换:直接使用Image对象生成缩略图,跳过MemoryStream转byte[]的步骤,降低内存占用:
    private static void CreateResizedImageFromOriginalImage(string ImageName, string ImageDimension, string newsizename, ref string returnName, ref string contentType)
    {
        using (System.Drawing.Image originalImage = ImageService.RetrieveImage(ImageName, true)) // 启用缓存检查
        {
            if (originalImage != null)
            {
                contentType = ImageService.RetriveImageContentType(ImageName);
                byte[] newdata = null;
                var dims = ImageDimension.Split('X');
                int imagewidth = dims.Length >= 1 && !string.IsNullOrEmpty(dims[0]) ? Convert.ToInt32(dims[0], System.Globalization.CultureInfo.InvariantCulture) : 0;
                int imageheight = dims.Length >= 2 && !string.IsNullOrEmpty(dims[1]) ? Convert.ToInt32(dims[1], System.Globalization.CultureInfo.InvariantCulture) : 0;
    
                // 修改ImageGenerator支持直接传入Image对象,避免内存转换
                newdata = ImageGenerator.CreateImageThumbnail(originalImage, contentType, imageheight, imagewidth, false);
    
                if (newdata != null && newdata.Length > 0)
                {
                    ImageService.SaveImage(newsizename, newdata, contentType);
                    returnName = ImageService.RetrieveImageUrl(newsizename);
                }
            }
        }
    }
    
  • 启用缓存机制:调用RetrieveImage时使用默认的checkCacheExpiration=true,利用缓存减少重复加载,降低内存和IO消耗。

3. 通用内存监控与预防

  • 内存快照分析:用Visual Studio内存分析器或dotMemory定期抓取内存快照,分析托管堆中的对象分布,定位未释放的HttpContext、Image对象或缓存条目。
  • 配置缓存过期策略:若用MemoryCache存储数据,设置合理的过期时间,避免缓存永久占用内存:
    var cache = MemoryCache.Default;
    var policy = new CacheItemPolicy
    {
        SlidingExpiration = TimeSpan.FromHours(2), // 2小时无访问自动过期
        Priority = CacheItemPriority.Default
    };
    cache.Set($"SavedCarts_{loggedinUser.Id}", savedCarts, policy);
    
  • 应用池定期回收:在IIS中设置应用池每天凌晨自动回收,作为兜底方案避免内存长期累积。

三、异常处理策略

  • 全局异常捕获:在Global.asax中注册Application_Error事件,捕获未处理的异常,记录日志并返回友好页面,避免应用崩溃:
    protected void Application_Error(object sender, EventArgs e)
    {
        var exception = Server.GetLastError();
        Logger.Fatal($"未处理全局异常:{exception.Message}", exception);
        Server.ClearError();
        Response.Redirect("/Error/ServerError");
    }
    
  • 内存不足降级处理:在关键代码块中捕获OutOfMemoryException,执行降级逻辑(如返回默认图片、跳过非关键缓存加载):
    try
    {
        // 图片处理或缓存加载代码
    }
    catch (OutOfMemoryException ex)
    {
        Logger.Error("内存不足,执行降级逻辑", ex);
        returnName = "/Images/DefaultProduct.jpg";
    }
    

内容的提问来源于stack exchange,提问作者Sandy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 04:35:46