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

ASP.NET应用Event句柄泄漏 大量ExpiresBucket实例异常排查

ASP.NET 内核Event句柄与内存泄漏问题解答

一、超大规模ExpiresBucket实例是否属于正常现象

完全异常。
System.Runtime.Caching.ExpiresBucket是MemoryCache内部用于跟踪缓存项过期时间的固定结构,单个MemoryCache实例初始化时只会生成固定数量的ExpiresBucket(数量和CPU核心数相关,单实例通常仅几十个,和缓存项总量无关)。你的转储中存在665万个ExpiresBucket、22万组配套的CacheExpires/MemoryCacheStore对象,唯一的解释是代码中持续创建新的MemoryCache实例,且这些实例从未被正确释放。

二、泄漏的Event句柄是否全部与缓存模块相关

缓存不是根因,只是泄漏链路的中间环节。
你贴的GCRoot引用链已经明确指向了泄漏源头:所有泄漏对象的根引用最终挂在Unity容器持有的Serilog日志组件实例上,链路中明确存在30万个以上被强引用持有、无法回收的System.Threading.TimerQueueTimer对象。
每个TimerQueueTimer初始化时都会创建一个内核态的ManualResetEvent类型Event句柄,这和你通过SysInternals检测到的每小时新增20万Event句柄、转储中22万个ManualResetEvent托管对象的现象完全匹配。
缓存相关对象出现在引用链里,是因为老版本Serilog.Sinks.Http的非持久化HttpSink内部会用MemoryCache做日志缓冲,每个未释放的HttpSink都会配套创建一个独立的MemoryCache实例、一个定时触发日志批量发送的PortableTimer,这些资源如果不显式调用Dispose释放,会被.NET运行时的Timer队列永久强引用,GC永远无法回收。

三、后续排查与修复方向

  • 优先检查Serilog初始化逻辑:全局搜索new LoggerConfiguration()、AddSerilog、new HttpSink相关代码,确认是否存在每次请求/每次业务操作都新建Logger实例的问题,正确做法是全局初始化一次Serilog Logger,作为单例复用。老版本Serilog.Sinks.Http(非Durable版本)存在未Dispose就泄漏Timer的已知问题。
  • 检查Unity容器注册配置:确认ILogger、LoggerFactory的生命周期是否被错误配置为瞬态(Transient),你当前用的Unity 3.5版本如果生命周期配置错误,每次Resolve服务都会生成新的Logger实例,最终被全局容器链路持有无法释放。
  • 全局排查MemoryCache用法:搜索所有new MemoryCache(的代码位置,业务开发优先使用MemoryCache.Default全局单例,自定义创建的MemoryCache必须在生命周期结束时显式调用Dispose,否则其内部的过期扫描Timer、分桶结构都会永久泄漏。
  • 快速验证方案:临时将Serilog的HttpSink替换为本地文件输出Sink,运行1-2小时后观察句柄增长速度、ExpiresBucket对象数量,如果增长停止即可直接确认问题点。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 22:27:27