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

时间:2010-06-24 16:41:59

标签: c# .net garbage-collection

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

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

5 个答案:

答案 0 :(得分:17)

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

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

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

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

这个三段论完全无效。这就是三段论:

  • 我有八夸脱的冰淇淋
  • 我在冰箱里有九夸脱冰淇淋的空间
  • 因此,如果我得到两夸脱的冰淇淋,有些东西会融化
事实上,你隔壁有一个整个仓库大小的冷藏设施。请记住,RAM只是一种方便快捷的方式,可以将物品存放在您需要的地方附近,比如冰箱。如果你有更多的东西需要存储,谁在乎你是否在本地用完房间?您可以随时弹出隔壁并将您使用的东西放在长期深度冻结中 - 页面文件。这不是方便,但没有融化

当进程耗尽虚拟地址空间时,会出现“内存不足”异常,而不会消耗系统中的所有RAM。当系统中的所有RAM都被消耗时,你不会收到错误,你会得到废话性能,因为操作系统正在花费所有时间从磁盘来回运行东西。

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

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

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

  • 工作集 - 即出于性能原因需要在RAM中的进程的虚拟内存页 - 所有进程的小于RAM可用吗?如果没有,那么很快就会发生颠簸,但不是OOM。

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

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

答案 1 :(得分:15)

如果所有具有非托管资源的类都实现IDisposable并且已经妥善处理(通过使用或尝试/最终),那么添加更多IDisposable实现将无济于事。

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

  1. 未妥善处理非托管资源
  2. 保留托管对象的大对象图
  3. 鉴于您问题中的信息,几乎可以肯定#2导致问题。您需要获取一个分析器或windbg来告诉您实际泄漏是什么以及哪些有根对象导致它。

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

答案 2 :(得分:5)

答案是,几乎肯定不是。您是否通过剖析服务来验证您实际上是否存在不应该存在的内存?

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

答案 3 :(得分:4)

简短回答:不。

更长的答案:Noooooo。

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

答案 4 :(得分:2)

衡量,衡量,衡量

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

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

如果您确定使用了过多的托管内存。您可以使用.Net memory profiler之类的工具或非常灵活但windbg + sos

的工具进一步细分用法

一旦你孤立了你的记忆,你可以看看减少使用的策略,它可能就像用Cache替换Dictionary或添加字符串构建器一样简单。

这种解决方案不太可能在所有东西上洒上一点点。