SetProcessWorkingSetSize 不推荐使用,它仅抖出物理内存页而不释放托管堆,可能加剧页面换入换出;真正应关注事件订阅、静态引用等托管内存泄漏及非托管资源的及时 Dispose。
为什么 SetProcessWorkingSetSize 看似有效但实际不推荐
它不是 gc,也不释放托管堆内存,只是把进程的工作集(物理内存页)“抖”回磁盘——操作系统可能下一秒又把它拉回来。对 .net 应用来说,这属于治标不治本,还可能触发更频繁的页面换入换出,反而拖慢响应。
- 只影响工作集大小,不影响
GC.GetTotalMemory返回的托管堆大小 - 在 Windows Server 或启用了 Lock Pages in Memory 权限的环境下可能被忽略
- 调用后任务管理器“内存(专用工作集)”数值下降,但“提交大小”和实际压力没变
- 现代 .NET(6+)默认启用
Server GC,配合后台 GC,手动干预工作集收益极低
真正该关注的托管内存泄漏点:事件订阅和静态引用
90% 的“内存下不去”问题,根源不在工作集,而在对象无法被 GC 回收。最典型的是长生命周期对象(如单例、静态类)持续持有短生命周期对象的引用,比如 UI 控件注册了未注销的事件。
-
+=订阅事件后,发布者会强引用订阅者,导致订阅者无法释放 - 静态集合(如
static List<object> Cache)不断 Add 却不 Remove 或 Clear - 使用
Timer时回调委托隐式捕获this,而 Timer 本身是静态/长期存活的 - 诊断建议:用
dotnet-dump analyze查!dumpheap -stat+!gcroot <obj_addr>定位根引用链
GC.Collect() 和 GC.WaitForPendingFinalizers() 什么时候能用
它们不是“清理内存”的快捷键,而是调试或极少数确定性场景下的辅助手段。盲目调用反而干扰 GC 的自适应节奏,尤其在高吞吐服务中。
- 仅在明确知道某批大对象刚被置为
null且后续无访问(如大型临时数据处理完),可考虑GC.Collect(2, GCCollectionMode.Forced) - 必须配对使用
GC.WaitForPendingFinalizers(),否则析构函数可能延迟执行,资源未及时释放 - .NET 5+ 中
GC.TryStartNoGCRegion更适合控制短时无 GC 场景,避免突发回收打断关键路径 - 永远不要在循环、高频事件或 ASP.NET Core 中间件里调用
GC.Collect
释放非托管资源:比“缩内存”更关键的一步
托管内存由 GC 管理,但文件句柄、GDI 对象、数据库连接、IntPtr 分配的内存这些,GC 不管——它们卡在系统层,会直接拖垮整个进程稳定性,甚至引发 OutOfMemoryException。
- 务必实现
IDisposable,并在Dispose()中调用CloseHandle、UnmapViewOfFile、FreeHGlobal等对应释放 API - 用
using块或await using确保及时释放,别依赖终结器(~MyClass())兜底 - 检查第三方库是否暴露
Dispose方法,比如Bitmap、HttpClientHandler、SqlDataReader - 工具验证:用 Process Explorer 查看进程的
GDI Objects和Handles数是否随操作稳定增长
Dispose 的那个 FileStream,或者还在被窗体静态事件拽着不肯走的 ViewModel。


















