.NET Core 8固定对象堆内存泄漏排查求助
固定对象导致的.NET Core内存泄漏排查问题
问题描述与已尝试调试
- 大型企业级.NET Core 8应用存在内存泄漏:大量固定对象阻碍GC堆压缩,导致堆内留存大量空闲内存
- Azure环境下内存超12GB时抓取内存转储,发现:
- 应用实际仅使用约200MB内存
- 第2代GC堆占9.5GB,其中99%为空闲内存;约3000个固定对象散布堆中,完全阻止堆压缩
- 泄漏的固定对象为
byte[8192],所有字节均为0,无法通过保留树追踪引用来源
- 已完成验证:项目未启用unsafe代码,代码中无
fixed关键字,通过CA2000规则确认所有可释放对象均已正确释放,但仍存在大量无法回收的固定对象
请求
- 确认对固定对象相关原理的理解是否正确
- 如何定位这些
byte[8192]数组的创建来源
项目详情
- 运行环境:.NET Core 8.0
- 问题触发条件:仅高负载时出现,无法提供内存转储或完整源码
- 使用的NuGet包:
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.22.0" /> <PackageReference Include="Microsoft.AspNetCore.SpaProxy" Version="8.0.11" /> <PackageReference Include="Selenium.WebDriver" Version="4.26.1" /> <PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.1" /> <PackageReference Include="Azure.Identity" Version="1.13.1" /> <PackageReference Include="AsyncAwaitBestPractices" Version="8.0.0" /> <PackageReference Include="Azure.ResourceManager" Version="1.13.0" /> <PackageReference Include="Azure.ResourceManager.CosmosDB" Version="1.3.2" /> <PackageReference Include="Azure.ResourceManager.ServiceBus" Version="1.0.1" /> <PackageReference Include="DistributedLock" Version="2.5.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" /> <PackageReference Include="MongoDB.Driver" Version="2.9.2" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Azure.Messaging.ServiceBus" Version="7.18.2" /> <PackageReference Include="Azure.Storage.Blobs" Version="12.22.2" /> <PackageReference Include="Microsoft.ApplicationInsights" Version="2.22.0" /> <PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.22.0" /> <PackageReference Include="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel" Version="2.22.0" /> <PackageReference Include="NeoSmart.AsyncLock" Version="3.2.1" /> <PackageReference Include="Microsoft.Azure.Cosmos" Version="3.45.0" /> <PackageReference Include="Braintree" Version="5.28.0" /> <PackageReference Include="System.Security.Cryptography.Xml" Version="8.0.2" />
- 部署实例差异分析:
- 实例1(存在泄漏):处理API请求 → 读取Cosmos → 反射处理 → 写入Cosmos → 发布Service Bus消息
- 实例2(无泄漏):处理Service Bus消息 → 读取Cosmos → 写入Cosmos
- 推测泄漏根源在API请求处理栈
- 项目中存在少量Reflection.Emit代码(仅执行8次并存储结果):
var typeSignature = this.GetType().Name + "Suffix"; var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(Guid.NewGuid().ToString()), AssemblyBuilderAccess.Run); var moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule"); var typeBuilder = moduleBuilder.DefineType(typeSignature, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout, null);
补充信息
- 发现29937个
Byte[][Pinned Handle]实例,同时存在相近数量的以下类型实例:- IOCompletionCallbackHelper
- FileSystemWatcher+ASyncReadState
- ThreadPoolBoundHandle
- PreAllocatedOverlapped
- ThreadPoolBoundHandlerOverlapped
解答
1. 固定对象原理理解确认
你的基础理解存在局限:
- 除了unsafe代码中的
fixed关键字,.NET运行时或框架/第三方库也可能创建固定对象,常见场景包括:- 异步IO操作(如
ThreadPoolBoundHandle、PreAllocatedOverlapped关联的缓冲区):这类操作会自动固定字节数组作为IO缓冲区,若关联资源未正确释放,固定句柄会残留 - P/Invoke互操作时的隐式固定:框架或第三方库的原生调用可能自动固定对象
- 部分序列化/反序列化库的内部实现逻辑
- 异步IO操作(如
- CA2000规则仅覆盖显式实现
IDisposable的对象,无法追踪运行时或框架隐式创建的固定资源
2. 定位byte[8192]数组的创建来源
结合你的场景(仅API实例泄漏、关联IO相关类型),按以下步骤定向排查:
针对API栈的重点排查
- 检查HTTP请求处理中的IO操作:
- 排查自定义请求/响应拦截器、中间件是否使用字节缓冲区且未正确释放关联资源
- 验证
Microsoft.AspNetCore.SpaProxy的配置与使用:SpaProxy在代理请求时可能使用内部8KB缓冲区,高负载下若资源回收不及时可能导致泄漏 - 检查文件上传/下载逻辑:这类操作常使用
FileStream或自定义缓冲区,若未正确处置可能关联固定对象
- 排查第三方库的隐式固定:
- 重点检查
Azure.Storage.Blobs:存储操作默认使用8KB缓冲区,若客户端实例未正确重用或处置,可能残留固定句柄 - 验证
Selenium.WebDriver的资源回收:WebDriver的网络请求或文件操作可能使用固定缓冲区,高负载下若未正确关闭会话会导致泄漏
- 重点检查
基于运行时诊断的技术手段
- 启用.NET Event Tracing (ETW):
- 使用
dotnet-trace工具收集包含GC分配事件的追踪日志,筛选byte[8192]的调用栈:dotnet-trace collect --providers Microsoft-Windows-DotNETRuntime:0x10000:5,Microsoft-Windows-DotNETRuntimeRundown:0x1000:5 --process-id <PID> - 用PerfView分析日志,定位分配
byte[8192]的代码路径
- 使用
- Azure诊断工具:
- 启用Application Insights Profiler或Snapshot Debugger,捕获高负载时的调用栈,关联固定对象的创建上下文
- dotnet-dump高级分析:
- 在内存泄漏触发时抓取转储,使用
!dumpheap -type Byte[]筛选固定实例,再通过!gcroot <对象地址>追踪关联的IO资源(如PreAllocatedOverlapped)
- 在内存泄漏触发时抓取转储,使用
Reflection.Emit代码的影响验证
你的Reflection.Emit代码不会导致固定对象泄漏:动态程序集的创建逻辑未涉及unsafe固定或IO操作,仅执行8次且存储结果的场景不会产生大量固定字节数组。
关联补充信息中的IO类型
你发现的ThreadPoolBoundHandle、PreAllocatedOverlapped等类型直接关联异步IO的固定缓冲区,说明泄漏根源是异步IO资源未正确释放:
- 检查是否存在未完成的异步IO操作,或
Overlapped对象未被回收 - 排查是否有自定义代码使用
ThreadPoolBoundHandle但未正确处置
内容的提问来源于stack exchange,提问作者user29409503
相关产品推荐
相关产品推荐

