.NET 6应用内存占用过高:大字符串驻留问题排查求助
.NET 6应用高内存占用问题分析与解决
问题背景
生产环境运行的.NET 6应用出现异常高内存占用:仅4个System.String实例就占用了2GB内存。通过dotnet-dump工具分析得到以下关键信息:
dumpheap -stat 输出
7f6f4964f8f0 18,152 1,466,966,959 System.Byte[] 7f6f48fad2e0 51,915 1,981,308,612 System.String
gcroot 分析结果(部分)
对大字符串地址执行gcroot后,发现其通过以下链被持留:
7f65298bf530 7f6f4facc029 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) r15: -> 7f6547f46900 System.Threading.Thread+StartHelper -> 7f66486d8ab8 System.Threading.ExecutionContext -> 7f664863e128 System.Threading.AsyncLocalValueMap+MultiElementAsyncLocalValueMap -> ... -> 7f654811f9c8 System.Diagnostics.Activity -> ... -> 7f695b756528 System.String
大字符串内容(dc命令读取)
字符串为JSON格式,包含大量类元数据、自动生成的闭包类(如<>c__DisplayClassXX_0)、方法调用细节等冗余内容:
{"Target":{"handler":{"Target":{"<>4__this":{},"act":{"Transfer":null,"TraceId":">>>>>aaa-432- ","TransferId":"15","SeqNum":"08","TriggeredBy":2,"TriggeredAt":"2024-12-․19T12:30:08.9597687Z"},"transfer":null,"blacklisted":[56]},"Method":{"Name":"<Initiate>g__Handler|0","DeclaringType":"ServiceName.Infrastructure.modules.ServiceOutgoing.Actions.ServiceActionAction+ <>c__DisplayClass10_0, ServiceName, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",...
已排查步骤:移除Sentry监控、移除所有日志组件,问题仍未解决;服务配置与其他微服务一致,未发现注册问题。
原因分析
- 大字符串的来源:字符串是包含闭包/委托的复杂对象序列化后的结果,其中自动生成的
<>c__DisplayClass系列类携带了大量冗余的程序集元数据,导致单个字符串体积异常庞大。 - 内存泄漏的核心原因:大字符串被
System.Diagnostics.Activity的标签列表持有,而Activity又被ExecutionContext中的AsyncLocal存储引用。由于线程池线程会复用ExecutionContext,Timer线程(从gcroot中可见TimerQueueTimer)会长期持有这些上下文,导致Activity及关联的大字符串无法被GC回收。 - 残留的AsyncLocal引用:即使移除了Sentry,之前的
AsyncLocal上下文没有被正确清理,线程池线程复用这些上下文时,会持续持有大字符串的引用。
解决方案
定位并修复Activity标签的错误设置
- 检查所有调用
Activity.AddTag、Activity.SetTag的代码,禁止将复杂对象(如委托、闭包实例)直接序列化后放入标签,仅保留必要的简单值(如TraceId、TransferId等)。 - 排查是否有第三方组件自动向Activity注入了冗余标签。
- 检查所有调用
确保Activity被正确回收
- 在请求处理完成或异步任务结束后,显式调用
Activity.Current?.Dispose(),触发Activity的清理逻辑,避免其被AsyncLocal长期持有。 - 若使用ASP.NET Core,确保请求结束时Activity的生命周期被正确管理(可通过中间件统一清理)。
- 在请求处理完成或异步任务结束后,显式调用
清理AsyncLocal上下文
- 检查是否有自定义的AsyncLocal存储,在使用完成后显式移除对应的值,避免上下文被线程池线程复用。
- 对于Timer任务,在任务执行完成后,手动重置或清理关联的ExecutionContext。
升级.NET 6补丁
- 升级到.NET 6的最新补丁版本,修复早期版本中Activity或AsyncLocal可能存在的内存泄漏bug。
内容的提问来源于stack exchange,提问作者Hassan Algadah
相关产品推荐
相关产品推荐

