C#开发中遇到程序内存溢出、无响应或者崩溃问题时,内存转储文件是定位根因的重要素材。通过ClrMD库可以脱离图形化工具,用代码直接解析转储文件,获取CLR运行时的各类关键信息。

ClrMD库环境准备
ClrMD是微软官方提供的CLR内存诊断类库,可通过NuGet直接安装。在Visual Studio的包管理控制台执行以下命令即可完成引用添加:
Install-Package Microsoft.Diagnostics.Runtime
安装完成后,项目会自动引用Microsoft.Diagnostics.Runtime命名空间下的相关类型,后续所有分析操作都基于该命名空间展开。
加载内存转储文件
分析的第一步是加载目标内存转储文件,ClrMD提供了DataTarget类型用于打开本地转储文件,同时需要获取对应版本的CLR运行时实例。
using Microsoft.Diagnostics.Runtime;
using System;
class DumpAnalyzer
{
static void Main(string[] args)
{
string dumpPath = @"C:tempcrash.dmp";
// 打开内存转储文件
using (DataTarget dataTarget = DataTarget.LoadDump(dumpPath))
{
// 获取CLR实例,转储文件可能包含多个CLR版本,这里取第一个
ClrRuntime runtime = dataTarget.ClrVersions[0].CreateRuntime();
Console.WriteLine($"已加载CLR版本:{dataTarget.ClrVersions[0].Version}");
}
}
}
遍历托管堆对象
托管堆是内存泄漏问题排查的核心区域,ClrMD可以遍历堆上所有存活的对象,统计类型分布和内存占用。
using Microsoft.Diagnostics.Runtime;
using System;
using System.Collections.Generic;
class HeapAnalyzer
{
static void Main(string[] args)
{
string dumpPath = @"C:tempcrash.dmp";
using (DataTarget dataTarget = DataTarget.LoadDump(dumpPath))
{
ClrRuntime runtime = dataTarget.ClrVersions[0].CreateRuntime();
ClrHeap heap = runtime.Heap;
// 统计各类型对象数量和总内存占用
Dictionary<string, (int count, ulong size)> typeStats = new Dictionary<string, (int, ulong)>();
// 遍历所有存活对象
foreach (ClrObject obj in heap.EnumerateObjects())
{
string typeName = obj.Type.Name;
if (typeStats.ContainsKey(typeName))
{
var oldVal = typeStats[typeName];
typeStats[typeName] = (oldVal.count + 1, oldVal.size + obj.Size);
}
else
{
typeStats[typeName] = (1, obj.Size);
}
}
// 输出占用内存前5的类型
Console.WriteLine("内存占用前5的类型:");
int index = 0;
foreach (var item in typeStats)
{
if (index >= 5) break;
Console.WriteLine($"类型:{item.Key},对象数:{item.Value.count},总大小:{item.Value.size}字节");
index++;
}
}
}
}
定位异常信息
如果程序是因为未处理异常崩溃,转储文件中会保留异常相关的调用栈和详细信息,ClrMD可以直接提取这些内容。
using Microsoft.Diagnostics.Runtime;
using System;
class ExceptionFinder
{
static void Main(string[] args)
{
string dumpPath = @"C:tempcrash.dmp";
using (DataTarget dataTarget = DataTarget.LoadDump(dumpPath))
{
ClrRuntime runtime = dataTarget.ClrVersions[0].CreateRuntime();
// 获取所有线程
foreach (ClrThread thread in runtime.Threads)
{
// 检查线程是否有未处理异常
ClrException exception = thread.CurrentException;
if (exception != null)
{
Console.WriteLine($"线程ID:{thread.ManagedThreadId} 发现异常");
Console.WriteLine($"异常类型:{exception.Type.Name}");
Console.WriteLine($"异常消息:{exception.Message}");
// 输出异常调用栈
Console.WriteLine("调用栈:");
foreach (var frame in exception.StackTrace)
{
Console.WriteLine($" {frame}");
}
}
}
}
}
}
常见注意事项
- 内存转储文件的位数需要和ClrMD运行环境的位数匹配,32位转储需要用32位进程加载,64位同理
- 分析时需要保证本地有对应版本的CLR符号文件,否则部分类型信息可能无法正确解析
- ClrMD不支持修改转储文件内容,仅能进行读取和分析操作
- 如果转储文件是完整转储,分析时可以获取更完整的堆和线程信息,小型转储可能存在数据缺失