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

.NET 4.8 Windows服务依赖解析时偶发OutOfMemoryException问题

问题分析与解决方案

核心问题定位

从错误栈和代码逻辑来看,OutOfMemoryException 发生在Autofac解析ILogger并注入HandleA的过程中,根源在于日志组件的注册方式、异步队列配置存在内存泄漏风险,加上高并发场景下的资源积累,最终引发内存耗尽。

具体问题点

  1. ILogger实例重复创建
    当前Autofac注册ILogger时未指定生命周期,默认采用InstancePerDependency(每次解析都新建实例)。每次解析HandleA/HandleB都会创建全新的Serilog Logger实例,每个实例都带有独立的异步文件写入器,大量重复实例会持续占用内存资源。

  2. 异步日志队列无界
    Serilog的WriteTo.Async()默认使用无界队列,当日志写入速度跟不上生成速度时,队列会无限增长,最终耗尽内存。

  3. 日志过滤逻辑错误
    第二个Logger的过滤条件e.Level == Verbose && e.Level == Debug存在逻辑错误(单个日志事件不可能同时属于两个级别),导致所有日志都会进入Map sink,增加了不必要的日志写入压力和内存占用。

  4. Handler实例未及时释放
    直接从根容器解析Handler且未手动释放,若Handler持有资源(如Logger),会导致对象无法被GC回收,长期积累引发内存泄漏。

分步解决方案

1. 将ILogger注册为单例

修改Autofac注册逻辑,让Serilog Logger全局复用同一个实例,避免重复创建:

builder.Register<ILogger>((c, p) =>
{
    return new LoggerConfiguration()
                .MinimumLevel.Verbose()
                .WriteTo.Logger(logger => logger
                    .Filter.ByIncludingOnly(e => e.Level == LogEventLevel.Verbose || e.Level == LogEventLevel.Debug)
                    .WriteTo.Async(asyncLog => asyncLog.File(
                        ApplicationConstants.ApplicationLogPath + @"\Trace\Trace-.txt",
                        LogEventLevel.Verbose,
                        ApplicationConstants.ApplicationLogTemplate,
                        rollingInterval: RollingInterval.Day,
                        rollOnFileSizeLimit: true,
                        fileSizeLimitBytes: 10000000,
                        shared: true)))
                .WriteTo.Logger(logger => logger
                    // 修正过滤逻辑:排除Verbose和Debug级别
                    .Filter.ByExcluding(e => e.Level == LogEventLevel.Verbose || e.Level == LogEventLevel.Debug)
                    .WriteTo.Map("LogContext", "Log",
                    (logContext, wt) => wt.Async(asyncLog => asyncLog.File(
                        ApplicationConstants.ApplicationLogPath + $@"\Logs\{logContext}-.txt",
                        LogEventLevel.Information,
                        ApplicationConstants.ApplicationLogTemplate,
                        rollingInterval: RollingInterval.Day,
                        rollOnFileSizeLimit: true,
                        fileSizeLimitBytes: 10000000,
                        shared: true))))
                .CreateLogger();
// 注册为单例,全局复用同一个Logger实例
}).SingleInstance();

2. 配置异步日志队列的有界容量

给WriteTo.Async()添加boundedCapacity参数,限制队列最大长度,避免无限增长:

// 示例:设置队列最大容量为10000,可根据服务器性能调整
.WriteTo.Async(asyncLog => asyncLog.File(...), boundedCapacity: 10000)

队列满时可通过overflowAction配置处理策略(如丢弃最新日志或阻塞),避免丢失关键日志。

3. 使用生命周期范围管理Handler实例

避免直接从根容器解析Handler,改用生命周期范围(Lifetime Scope),确保使用完后释放资源:

// 在处理数据的代码中,创建独立的生命周期范围
using (var scope = ContainerManager.Container.BeginLifetimeScope())
{
    switch(type)
    {
        case "A":
            return scope.ResolveKeyed<IHandler>("HandleA");
        case "B":
            return scope.ResolveKeyed<IHandler>("HandleB");
    }
}

若Handler需要在using块外使用,需确保后续手动释放,或调整其注册策略为InstancePerLifetimeScope。

4. 优化Handler的资源释放

让Handler实现IDisposable接口,确保资源能被正确回收:

public class HandleA: IHandler, IDisposable
{
    private ILogger _fileLogger;
    public HandleA(ILogger fileLogger)
    {
        _fileLogger = fileLogger;
    }

    public void Dispose()
    {
        // 若Logger或其他依赖需手动释放,在此处理
        // Serilog Logger通常无需手动释放,如有自定义扩展则需对应处理
    }
}

5. 添加内存监控日志

在服务器上添加定期内存监控,便于排查后续问题:

// 定期执行(如每小时)
var process = Process.GetCurrentProcess();
_logger.Information("当前进程内存使用:{MemoryMB} MB", process.WorkingSet64 / 1024 / 1024);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 20:13:11