.NET 4.8 Windows服务依赖解析时偶发OutOfMemoryException问题
核心问题定位
从错误栈和代码逻辑来看,OutOfMemoryException 发生在Autofac解析ILogger并注入HandleA的过程中,根源在于日志组件的注册方式、异步队列配置存在内存泄漏风险,加上高并发场景下的资源积累,最终引发内存耗尽。
具体问题点
ILogger实例重复创建
当前Autofac注册ILogger时未指定生命周期,默认采用InstancePerDependency(每次解析都新建实例)。每次解析HandleA/HandleB都会创建全新的Serilog Logger实例,每个实例都带有独立的异步文件写入器,大量重复实例会持续占用内存资源。异步日志队列无界
Serilog的WriteTo.Async()默认使用无界队列,当日志写入速度跟不上生成速度时,队列会无限增长,最终耗尽内存。日志过滤逻辑错误
第二个Logger的过滤条件e.Level == Verbose && e.Level == Debug存在逻辑错误(单个日志事件不可能同时属于两个级别),导致所有日志都会进入Map sink,增加了不必要的日志写入压力和内存占用。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

