用 WinDbg 分析 .NET 内存 Dump 需先确认用户模式,再加载匹配版本的 SOS 扩展(.NET Framework 用 .loadby sos clr,.NET Core/5+ 用 .load 指定 sos.dll 路径),运行 !eeversion 验证;接着用 !dumpheap -stat 查对象分布,!dumpheap -type 定位类型,!gcroot 追踪根引用,优先分析 LOH 大对象;注意架构与符号匹配,避免误用 WinDbg Preview 或错误位数工具。

怎么用 WinDbg 打开 .NET 内存 Dump 文件
WinDbg 本身不直接理解 .NET 对象模型,必须配合 SOS 或 SOSEX 扩展才能分析托管堆。直接双击打开 .dmp 文件后,如果没加载扩展,所有 !dumpheap、!gcroot 都会报错:The request is not supported 或 Unknown command。
- 先确认是用户模式 dump(不是内核 dump):用
file命令看输出里是否有user-mode minidump - 加载 SOS:.NET Framework 用
.loadby sos clr;.NET Core / .NET 5+ 必须用.load C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.27\sos.dll(路径按实际版本调整) - 验证是否成功:运行
!eeversion,有输出说明 SOS 加载成功;否则检查 .NET 运行时版本和 SOS 是否匹配 - 别用 WinDbg Preview 直接开高版本 .NET dump——它默认附带的 SOS 可能太旧,优先用命令行版 WinDbg x64 + 手动指定
sos.dll
怎么快速定位大对象和根引用链
内存泄漏通常表现为某类对象数量持续增长、或大对象堆(LOH)膨胀。不能只看总数,得看谁在 hold 住它们。
- 查所有对象类型分布:
!dumpheap -stat,重点关注System.Byte[]、String、自定义类名(如MyService),按字节数倒序看前几项 - 查某个类型实例列表:
!dumpheap -type MyNamespace.MyHandler,再挑一个地址(比如000001a2f3456789)跑!gcroot -all 000001a2f3456789 -
!gcroot输出里如果出现HandleTable,大概率是静态事件没注销、Timer没Dispose、或缓存没清理;出现FinalizerQueue则可能 Finalizer 线程卡住 - 避免对整个堆跑
!gcroot——太慢。先用!dumpheap -min 85000锁定 LOH 上的大数组,再逐个查根
为什么 !dumpheap -live 和任务管理器内存不一致
因为 !dumpheap 只统计托管堆已分配但尚未回收的对象,而任务管理器显示的是进程工作集(Working Set),包含本机内存、JIT 代码、线程栈、未映射的虚拟内存等。
-
!dumpheap -stat的 Total Size 通常远小于私有工作集(Private Working Set),差值就是非托管部分 - 真正反映托管内存压力的是
!eeheap -gc输出的 GC 堆大小,以及!dumpheap -min 0 -max 0统计的空闲空间比例 - 如果
!dumpheap -stat总量不大,但进程内存持续上涨,问题大概率在非托管侧:比如 P/Invoke 调用的 C++ 库没释放内存、Marshal.AllocHGlobal没配对FreeHGlobal、或Bitmap/Stream未Dispose
常见误操作:用错了调试符号或架构不匹配
WinDbg 分析失败,八成是因为符号路径或位数搞错了。x64 进程 dump 用 x86 WinDbg 打开,或者符号没加载,!clrstack 就只能看到 ??? 。
- 务必用和 dump 文件相同架构的 WinDbg:x64 dump → WinDbg x64;ARM64 dump → WinDbg ARM64
- 设置符号路径:
.sympath srv*C:\symbols*https://msdl.microsoft.com/download/symbols,然后.reload - 检查模块状态:
lm看clr.dll或coreclr.dll是否 loaded;若显示deferred,说明符号没命中,要确认路径和版本 - .NET Core dump 中,
coreclr.dll符号必须和运行时完全一致——不同 patch 版本的 SOS 不通用,比如 6.0.27 的 dump 不能用 6.0.25 的sos.dll
最麻烦的不是命令不会输,而是 dump 生成时机不对:程序还在跑就用 Task Manager 直接“创建转储”,容易拿到不一致的状态;应该用 procdump -ma -e 1 -f "OutOfMemory" MyApp.exe 或 dotnet-dump collect -p 来抓可靠快照。










