跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

C# 内存泄漏排查方法 C#如何使用dotMemory或VS诊断工具查找泄漏

dotMemory通过快照对比Surviving Objects和Retained Size快速定位托管对象泄漏,重点分析Shortest Path to Root及GC Roots类型,并配合VS诊断工具分阶段验证。 dotMemory 怎么快速定位托管对象泄漏 dotMemory 能直接告诉你哪些类型占内存最多、哪些对象没被回收,比纯看 GC 日志直观得多。启动后选择「Profile Application」→ 附加到正在运行的 C# 进程(或直接启动),跑完操作后点「Take Snapshot」,重点看
Surviving Objects
和
Retained Size
列。 常见误判点:只看
Instance Count
上升不等于泄漏——得确认这些对象在多次快照间持续存在、且引用链里没有你预期的释放逻辑。比如一个
Timer
持有
Form
实例,而
Form
又持有大量控件和事件委托,这个引用链会阻止整个对象图回收。 用
Group by Type
找出数量/大小异常增长的类,双击进去看具体实例 右键某个可疑实例 →
Shortest Path to Root
,查谁在强引用它(注意
GC Root
类型:
Static Field
、
Finalizer Queue
、
Thread Local Storage
都是高危信号) 对比两个快照时,勾选
Show only objects created after snapshot #1
,过滤掉初始化阶段的“噪音” Visual Studio 自带诊断工具能查到什么程度 VS 2019+ 的「Diagnostic Tools」窗口(调试时按
Ctrl+Alt+F2
)适合轻量级排查,但只能捕获调试中的一小段内存行为,不能像 dotMemory 那样做长时间驻留分析。它更擅长发现短期突增,比如某次按钮点击后
byte[]
突然暴涨却没释放。 关键限制:它不显示完整引用链,也不支持跨快照对比;
Memory Usage
图表下点「Take Heap Snapshot」后,只能看到当前堆上存活对象的类型分布,无法追溯“为什么没被回收”。 调试时打开「Diagnostic Tools」→ 勾选「Memory Usage」→ 操作复现问题 → 点「Take Heap Snapshot」 在快照列表里点「Objects > Group by Type」,找
Retained Bytes
高的类型,再点开看「Instances」里的具体对象 如果看到大量
WeakReference
或
EventHandler
实例堆积,大概率是事件没反订阅,尤其注意
+=
后没配
-=
哪些 C# 代码模式最容易触发泄漏(VS/dotMemory 都难一眼看出) 工具能暴露现象,但根源往往藏在编码习惯里。下面几种情况,即使快照里对象数量不多,也可能因间接引用导致整块内存无法回收: C知道 CSDN推出的一款AI技术问答工具 下载
static
字段缓存了非
WeakReference
的 UI 控件或数据容器(如
static Dictionary>
) 使用
Task.Factory.StartNew
但没传
CancellationToken
,后台任务卡在 I/O 或等待中,持续持有闭包变量 WPF 中绑定
DataContext
后,没清理
INotifyPropertyChanged
的订阅者,或用了
BindingOperations.EnableCollectionSynchronization
却忘了
Disable
第三方库内部持有了
DispatcherTimer
或
CompositionTarget.Rendering
事件,而你没调用其提供的
Dispose()
方法 这类泄漏在 dotMemory 的
Shortest Path to Root
里可能只显示为「
Finalizer Queue
→
SomeLibrary.InternalManager
」,得结合源码或文档反推。 dotMemory 和 VS 工具怎么配合用才不重复踩坑 别指望一个工具解决所有问题:VS 适合开发阶段快速验证单次操作,dotMemory 才是上线前压测或用户反馈“越跑越慢”时的主力。两者配合的关键是统一关注点——不是“内存多大”,而是“哪些对象不该活这么久”。 先用 VS 在调试中确认泄漏可复现,记录操作路径(比如“登录→进主窗体→切换三个 Tab→退出”) 用 dotMemory 录制完整流程,至少取三个快照:操作前、操作中、操作后 30 秒,观察对象生命周期 如果 dotMemory 显示某类对象
Retained Size
持续增长,但
Shortest Path to Root
里只有系统线程(如
ThreadPoolWorkerThread
),就要怀疑异步回调没完成或
async void
导致异常吞没 真正麻烦的是混合泄漏:托管对象没释放,同时又 pinning 了非托管资源(比如
Bitmap
+
LockBits
),这时候得交叉看 dotMemory 的托管堆和 WinDbg 的
!dumpheap -stat
输出。

相关文章