C#内存分析工具(DotMemory使用)
一、为什么需要dotMemory?
当你发现在重复执行某个动作后,内存只增不减,可用内存越来越少;或者因为内 存问题导致卡顿;或者代码产生了过多,过大的临时对象,导致垃圾回收频繁触发,拖 慢程序运行速度时,dotMemory可以帮你看清内存中到底发生了什么,并帮你找到“罪 魁祸首”。
dotMemory官方文档: https://www.jetbrains.com/zh-cn/help/dotmemory/2025.3/dotMemory_Introduction.html
二、dotMemory内存问题排查
1.开始分析会话
在Profiling菜单中点击上方新建按钮“+”,选择Standalone(桌面或控制台程序),选择要分析的exe路径,保存,接下来进行配置分析会话选项,Sampled满足大多数场景,图中1,进程筛选根据需要可以自定义,默认就选整个进程树,图中2,控制分析方式选手动,图中3。点击Start按钮将启动程序开始分析;也可选择左下角运行中的程序启动分析。


2.获取快照
选择合适的时机点击Get Snapshot获取想要的快照,快照是应用程序托管堆的即时映像,根据当前怀疑某个动作导致了内存问题在其前后分别拍照,用于比较。单个快照基础信息包含total:整个应用程序消耗的内存大小,.net total:托管堆中的内存总量,包含空闲内存,.net used:应用程序使用了的托管堆中的内存,其余为非托管内存

当对象刚被创建时,它们会被放置在第 0 代(Gen 0)。 当第 0 代已满时,GC 会执行一次垃圾回收。 在回收过程中,GC 会从堆中移除所有不可达的对象。 所有可达的对象会被提升到第 1 代(Gen 1)。 当第 1 代已满时,会执行第 1 代的垃圾回收。 所有在回收中存活的对象会被提升到第 2 代(Gen 2)。 第 0 代的回收也会在此时进行。 当第 2 代已满时,GC 会执行完整垃圾回收。 首先执行第 2 代的回收,之后依次执行第 1 代和第 0 代的回收。 如果仍然没有足够的内存进行新的分配,GC 会引发 OutOfMemory 异常。
3.大对象堆
在.NET中,大对象堆(Large Object Heap, LOH)是一块特殊的内存区域,专门用于存放大型对象。下表概括了LOH的核心特性
|
特性 |
描述 |
|
阈值 |
对象大小>85KB |
|
回收世代 |
仅在 第2代垃圾回收(Full GC) 时被回收 |
|
内存整理 |
默认不进行压缩整理(但可通过API或高版本.NET优化) |
|
典型对象 |
大型数组(如 byte[], int[],double[],float[])、长字符串等 |
LOH的运作细节及性能
- 分配成本较高:CLR为确保新对象内存的清洁,会在分配时进行清零操作。对于大对象,这个过程需要消耗较多的CPU周期
- 容易产生内存碎片:这是LOH最主要的问题。由于默认不压缩,当多个大对象被分配和释放后,堆上会留下许多不连续的“空闲空间”。即使总空闲内存很大,也可能因为没有足够大的连续空间来分配一个新对象,导致触发不必要的完整垃圾回收,甚至引发OutOfMemoryException
- 对引用类型数组的特别关注:如果LOH上存放的是包含大量引用类型元素的数组(例如object[]或自定义类数组),GC在标记存活对象时需要遍历这些引用,会增加GC的负担。如果元素是值类型(如int, double),则GC无需处理其内部。
优化策略
- 首选对象复用:避免频繁地创建和销毁大对象。使用对象池(Object Pooling) ,特别是 ArrayPool<T>类,它可以池化数组供临时使用,用完后归还,极大地减少LOH的压力和碎片化
- 手动压缩LOH:在.NET Framework 4.5.1及更高版本(包括.NET Core/.NET 5+)中,可以通过设置 GCSettings.LargeObjectHeapCompactionMode属性,让下一次完整的阻塞式GC对LOH进行压缩 GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; GC.Collect();
- 避免固定大对象:使用fixed语句或GCHandle固定大对象会阻止GC移动它们,这可能加剧碎片化问题。应尽量避免长时间固定大对象
4.观察内存快照
点击快照名称打开快照,可以点击+按钮对相同数据执行多个独立的分析


观察内存快照--Inspections(检查):显示主要的快照热点
- Largest Size(最大大小) :该图表显示了特定类型对象占用的内存量。
- Largest Retained Size(最大的保留大小) :该图表显示了关键对象,即在内存中保留应用程序中所有其他对象的对象。
- String duplicates, Sparse arrays,Event handlers leak,等:dotMemory 会自动检查快照中最常见的内存问题类型。
- 堆碎片 :显示了托管堆段的碎片情况: 第 1 代、 2 和 大对象堆。
在Types视图中查看哪些对象占用了大量内存

当前视图按消耗内存量排序,图中大部分内存被Single[]占用,双击打开Single[],或右键点击选择打开此对象集,选择Back Traces(分析会话如果选择Sampled可能不显示调用路径), 可以看到这些Single[]数组来源WaveDataPreview.GenerateAdvancedWaveTest1()方法
5.比较快照
在可疑操作前后分别获取快照用于比较,点击下方Compare进入比较界面,在该界面可以看到两次快照中当前存活对象,新对象,死对象,通过内存变化量进行排序查看什么类型在涨


选中增长最多的类型,点击存活对象,死对象,新对象下方的数字,查看具体的对象实例,查看对象的详细信息

6.分析内存分配
在时间轴上选择区间,打开“内存分配”视图,分析哪些类型被造得最多,谁造的,调用链是什么,哪个模块干的?图中1、2、3都可打开内存分配视图


7.内存泄漏排查举例
在给lightingchart控件添加曲线时将该曲线也添加到另一集合中,重复操作,内存持续增长,调用清理,内存正常


总结
通过上面的方法可以快速分析C#程序内存占用情况,并定位到对应的代码中。利于我们快速优化内存占用问题。
工具下载地址:【免费】dotMemory工具资源-CSDN下载
更多推荐

所有评论(0)