clrmd 是微软官方 .net 内存分析 sdk,专用于离线解析 dump 文件,适用于服务器环境批量分析崩溃或内存泄漏;需匹配运行时版本、架构及符号路径,通过类型筛选高效查大对象与字符串泄漏,并手动提取线程栈和异常信息。

ClrMD 是什么,什么时候该用它
ClrMD 是微软官方提供的 .NET 内存分析 SDK,专用于离线解析 dump 文件(如 Windows 的 full dump 或 minidump),不是实时调试工具,也不能替代 Visual Studio 或 dotMemory。它适合在没有图形界面、无法装调试器的服务器环境里,用代码批量分析崩溃或内存泄漏问题。
如果你拿到的是 *.dmp 文件,且想写 C# 程序自动提取 GC 堆对象统计、查找大对象、定位 String 泄漏、枚举线程调用栈——ClrMD 就是目前最直接的选择。
安装与基础初始化要注意什么
必须匹配目标 dump 的 .NET 运行时版本:.NET Framework 4.x dump 要用 Microsoft.Diagnostics.Runtime 2.x;.NET Core/.NET 5+ dump 必须用 2.2.0+(推荐 2.2.416702),否则会报 Failed to initialize runtime 或直接抛 NullReferenceException。
- 用 NuGet 安装:
Install-Package Microsoft.Diagnostics.Runtime(注意选对版本) - 确保程序运行时架构(x64/x86)和 dump 架构一致;x64 dump 在 x86 进程里打开会失败
- 初始化前检查符号路径:
DataTarget.LoadCrashDump不自动下载 PDB,需手动设SymbolLocator或提前把 PDB 放到 dump 同目录
怎么读取托管堆并找出可疑大对象
核心流程是:加载 dump → 获取 CLR 运行时 → 枚举所有对象 → 按类型/大小筛选。别直接调 Heap.EnumerateObjects(),它返回的是地址,得配合 GetObjectSize() 和 GetTypeByMethodTable() 才能还原类型信息。
常见误操作是遍历全部对象再过滤,实际应先用 Heap.FindObjectByType() 或 Heap.EnumerateObjectAddressesByType() 缩小范围,否则在 10GB+ dump 里可能卡死或 OOM。
- 查所有
byte[]实例大小总和:heap.EnumerateObjectAddressesByType("System.Byte[]").Sum(addr => heap.GetObjectSize(addr)) - 找单个大于 10MB 的字符串:
heap.EnumerateObjectAddressesByType("System.String").FirstOrDefault(addr => heap.GetObjectSize(addr) > 10 * 1024 * 1024) - 获取对象内容(如字符串值)需用
ReadObject<string>(addr)</string>,但仅当对象未被压缩/移动且类型可序列化时才安全
线程栈和异常信息怎么提取
ClrMD 不提供“一键导出所有线程完整调用栈”的封装方法,必须手动遍历每个线程的 StackWalk,且 .NET Core dump 中栈帧可能缺少托管方法名(尤其没带 PDB 时)。关键点在于:先用 Runtime.Threads 获取线程列表,再对每个线程调 GetStackTrace(),但返回的 ClrStackFrame 里 Method 字段常为空,得靠 Module + IP + 符号定位反推。
- 只读托管异常信息(如最后未处理异常):
crashInfo.Exception(仅 full dump 有,minidump 通常为空) - 查某线程是否在执行
HttpClient.SendAsync:thread.GetStackTrace().Any(f => f.Method?.Contains("SendAsync") == true) - 若
Method为 null,尝试用f.Module.TryGetSymbolName(f.IP, out string name)补充,但依赖 PDB 可用性
真正难的是跨 native/managed 边界的栈帧识别,ClrMD 默认不解析 native 帧,遇到 clr!JIT_Throw 或 ntdll!NtWaitForSingleObject 就断了——这时候只能结合 WinDbg 的 !clrstack -a 做交叉验证。











