如何使用IDisposable来修复内存泄漏

我有一个.net应用程序似乎有内存泄漏问题。 .net服务启动大约100MB的内存,但在负载下它大约400-500MB。 我的大多数类没有非托管资源,而且已经实现了IDisposable。 所以我的问题是在我的课程帮助上打字IDisposable?

4-500 MB本身并不关心。 关注的是有8种不同的服务。 每个都是使用SharpArch,NServiceBus,Windsor和NHibernate构建的。 我的感觉是,其中一个中的某些东西导致了问题。 我担心的是,所有服务的总内存大约是3.2到3.6 gig的内存。 它还没有抛出OutOfMemoryexception,但我想在传球时把它关掉。 我也使用了dotTrace,它给了我一些信息,我只是不确定如何对这些信息采取行动

我首先关心的是确保你测量相关的东西。 “记忆”可能意味着很多不同的东西。 耗尽虚拟内存空间和耗尽RAM之间存在巨大差异。 由于破坏页面文件导致的性能问题与因创建过多GC压力而导致的性能问题之间存在巨大差异。

如果您不理解RAM,虚拟内存,工作集和页面文件之间的关系,那么首先要做一些阅读,直到您了解所有这些内容为止。 你提出这个问题的方式让我怀疑你相信虚拟内存和RAM是一回事。 他们当然不是。

我怀疑你正在做的算术是:

  • 我有八个进程,每个进程消耗5亿字节的虚拟地址空间
  • 我有40亿字节的RAM
  • 因此,我即将获得OutOfMemoryexception

这种三段论完全无效。 这是三段论:

  • 我有八夸脱的冰淇淋
  • 我在冰箱里有九夸脱的冰淇淋
  • 因此,如果我得到两夸脱的冰淇淋,有些东西会融化

事实上,你隔壁有一个整个仓库大小的冷藏设施。 请记住,RAM只是一种方便快捷的方式,可以将物品存放在您需要的地方附近,比如冰箱。 如果你有更多的东西需要存储,谁在乎你是否在本地用完房间? 您可以随时弹出隔壁并将您使用的东西放在长期深度冻结中 – 页面文件。 那不太方便 ,但没有什么能融化

当进程耗尽虚拟地址空间时,而不是当系统中的所有RAM都被消耗时,会出现“内存不足”exception。 当系统中的所有RAM都被消耗时,您不会收到错误,因为操作系统会花费所有时间从磁盘来回运行内容,所以您会得到废话

因此,无论如何,首先要了解您正在测量的内容以及Windows中的内存如何工作。 你应该寻找的是:

  • 是否有任何进程在32位系统上使用超过20亿字节的虚拟内存? 一个进程只能获得2GB的虚拟内存(不是RAM,请记住,虚拟内存与RAM无关: 这就是为什么 win32上称为“虚拟” – 它不是硬件 ),可由用户代码查询; 如果你试图使用更多,你会得到一个OOM。

  • 是否有任何进程存在尝试分配大量虚拟内存的危险,以至于没有该大小的连续块空闲? 例如,您是否可能在一个arrays中分配一千万字节的数据? 再次,OOM。

  • 工作集 – 即出于性能原因需要在RAM中的进程的虚拟内存页 – 是否所有进程都小于可用的RAM量? 如果没有,那么很快你就会吵架,但不会是OOM。

  • 您的页面文件是否足够大,可以处理虚拟内存页面,如果RAM开始变短,可以将其分页到磁盘?

到目前为止,这些都与.NET没有任何关系。 一旦你确实确定存在真正的问题 – 可能没有 – 然后根据真正的问题开始调查。 使用内存分析器检查内存分配器和垃圾收集器正在执行的操作。 查看大对象堆中是否存在大块,或者是否存在无法收集的活动对象的意外大图,或者是什么。 但要运用良好的工程原理:了解系统,使用工具调查实际的经验性能,试验变化并仔细测量其结果。 不要只是开始在几个类上随机拍打魔术IDisposable接口,并希望这样做会导致问题 – 如果有的话 – 消失。

如果所有具有非托管资源的类都实现了IDisposable并且被正确处理(通过使用或try / finally),那么添加更多IDisposable实现将无济于事。

第一个问题是你不知道为什么你会泄漏。 托管应用程序通常由于以下原因之一而泄漏

  1. 未正确处理非托管资源
  2. 保持托管对象的大对象图

鉴于您的问题中的信息,几乎可以肯定#2导致问题。 您需要获取一个分析器或windbg来告诉您实际泄漏是什么以及哪些有根对象导致它。

这是Rico的一篇很棒的文章,可以帮助您入门

答案是,几乎肯定不是。 您是否通过剖析服务确认您实际上是在抓住您不应该记忆的内存?

请记住,垃圾收集器可能不一定在需要之前释放内存,因此它分配400-500mb可能并不罕见。 我担心的一点是,在[插入合理的一段时间]之后,它会进一步爬升并达到1Gb,即使它没有处于任何更高的负载水平之下。

简答:没有。

更长的答案:Noooooo。

我想你已经知道了 – 如果你没有,你就不会在需要它的类上“打耳光”IDisposable – IDisposable与GC无关。 这里唯一真正重要的是你是否已经无条件地将Finalizers(〜classname)放在你的对象上 – 这将导致它们在获得GC之前在单线程终结器队列中备份,而不管它们是否包含非托管资源或不。

测量,测量,测量

如果您想减少应用程序的内存使用量,您需要先确定它的使用位置。

你可以通过在“私有字节,所有堆中的字节,大对象堆中的字节,gen 1,gen 2”等上添加一些perfmon计数器来获得一个粗略的想法。

如果您确定使用过多的托管内存。 您可以使用.Net内存分析器等工具或非常灵活但windbg + sos进一步细分使用

一旦你分离了内存的位置,你就可以看看减少使用的策略,它可能就像用Cache替换Dictionary或添加字符串生成器一样简单。

解决方案不太可能在所有东西上洒上IDisposable。