内存泄漏需通过三次快照差值分析:操作前拍#1,操作后暂停拍#2,重复操作再拍#3;在difference视图对比#3−#2与#2−#1,重点关注稳定新增的对象类型及字节数,结合path to root定位gc根引用,区分托管堆堆积与本机泄漏。

直接看内存快照差值,别只盯着“总内存”数字——增长是否来自托管对象持续堆积?还是本机堆泄漏?或者 GC 根没释放导致对象活过预期生命周期?
用 Memory Usage 工具拍三次快照比对
内存不断增长不等于一定泄漏,但稳定增长(比如每次操作后堆大小+5MB)就是强信号。关键不是单次快照,而是差值:
- 在疑似泄漏操作前打
Snapshot #1 - 执行一次完整操作(如打开一个窗体、加载一批数据、触发一次循环任务),暂停,打
Snapshot #2 - 重复同样操作再打
Snapshot #3 - 切换到
Difference视图,选#3 - #2和#2 - #1对比,重点关注“新增对象数”和“新增字节数”列
如果某个类型(比如 List<string></string> 或你自定义的 WorkStation)在每次差值中都稳定新增几百个实例,基本锁定问题源头。
检查 GC 根是否意外持有了对象
托管堆上对象不被回收,90% 是因为还有活跃的 GC 根引用它。常见陷阱包括:
- 静态集合(
static List<t> cache</t>、static Dictionary<k></k>)不断 Add 却没 Clear 或 Remove - 事件订阅未取消(
someObj.SomeEvent += Handler但没配对的-=),导致发布者长期持有订阅者引用 - Timer 回调中捕获了外部变量,且 Timer 没 Stop/Dispose,形成隐式根
- WPF/WinForms 控件未正确卸载(比如动态添加的 UserControl 没从 VisualTree 移除,也没调用
DataContext = null)
在快照对比视图中双击可疑类型 → 点 Path to Root,看哪些根(Static variable、Finalizer queue、Handle)在维持它的存活。
Visual Studio 18.8.1 官方固定版本安装引导程序,当前条目使用微软发布历史中的 Professional Web Installer,适合旧项目兼容、环境回退、复现特定构建链和排查版本差异等场景。
区分托管泄漏和本机泄漏
如果 Memory Usage 显示托管堆(.NET Heap)增长平缓,但进程私有工作集(Private Bytes)或 Native Heap 持续飙升,大概率是本机泄漏:
- C++/Qt 插件中 malloc/new 分配后没 free/delete(尤其跨 DLL 边界时)
- COM 对象未 Release(
pUnk->Release()缺失) - GDI 句柄、User 对象(窗口、菜单)创建后未 Destroy
- 使用了自定义分配器但没加
__declspec(allocator),导致 VS 无法跟踪分配点
此时要切到 Native Heap 标签页,或用 Debug > Windows > Show Diagnostic Tools > Native Memory(VS 2022 17.8+),按模块或调用栈看谁在大量申请本机内存。
别忽略调试配置和符号加载
很多“看不出泄漏”的情况,其实是工具没拿到足够信息:
- 确保项目是
Debug配置,且启用了调试信息(/Zifor C++,DebugType = fullfor C#) - 分析 Dump 时,必须加载对应 PDB:在
Debug > Options > Symbols中添加符号服务器路径,勾选Microsoft Symbol Servers - 若用
.NET Object Allocation Tracking,注意它默认采样,高频小对象可能被跳过;需进齿轮设置调低采样间隔(如1ms)才能抓到短生命周期对象 - 对于混合模式(.NET + C++),务必同时启用
Managed Heap和Native Heap跟踪,否则会漏掉跨边界的引用链
最易被跳过的点:快照时间点没卡准——在对象刚分配完、GC 还没跑之前拍,和等 GC 完全回收后再拍,看到的“增长量”能差一个数量级。










