哪些策略和工具可用于发现.NET中的内存泄漏?


152

我写了10年的C ++。我遇到了内存问题,但是可以通过合理的努力解决它们。

在过去的几年中,我一直在编写C#。我发现我仍然遇到很多内存问题。由于不确定性,它们很难诊断和修复,并且因为C#的理念是,当您一定要这样做时,您不必担心这些事情。

我发现的一个特殊问题是,我必须明确地处置和清理代码中的所有内容。如果我不这样做,那么内存探查器并没有真正的帮助,因为有太多杂乱无章的东西浮在水面上,您无法在他们试图向您展示的所有数据中找到泄漏。我想知道我是否有错误的主意,或者我拥有的工具不是最好的。

什么样的策略和工具对于解决.NET中的内存泄漏很有用?


您的帖子标题与您帖子中的问题并不完全匹配。我建议您更新标题。
凯文

你是对的。抱歉,我对当前正在寻找的泄漏感到有些烦恼!标题已更新。
Scott Langham

3
@Scott:不要厌倦.NET,这不是问题。您的代码是。
GEOCHET

3
是的,我的代码或我很乐意使用的第三方库。
Scott Langham

@斯科特:看我的答案。MemProfiler是值得的。使用它还将使您对.NET GC世界有一个全新的了解。
GEOCHET

Answers:


51

当我怀疑内存泄漏时,我使用ScitechMemProfiler

到目前为止,我发现它非常可靠且功能强大。它至少一次拯救了我的培根。

GC在.NET IMO中工作得很好,但是就像其他任何语言或平台一样,如果编写不好的代码,也会发生不好的事情。


3
是的,我对此很满意,它帮助我解决了一些棘手的漏洞。我发现最大的泄漏是由第三方库通过互操作访问的非托管代码引起的。令我印象深刻的是,该工具检测到非托管代码和托管代码中的泄漏。
Scott Langham

1
我接受了这个作为答案,因为它最终对我有用,但是我认为所有其他答案都非常有用。顺便说一下,该工具通常称为SciTech的Mem Profiler!
Scott Langham

41

仅为了解决遗忘问题,请尝试本博客文章中描述的解决方案。这是本质:

    public void Dispose ()
    {
        // Dispose logic here ...

        // It's a bad error if someone forgets to call Dispose,
        // so in Debug builds, we put a finalizer in to detect
        // the error. If Dispose is called, we suppress the
        // finalizer.
#if DEBUG
        GC.SuppressFinalize(this);
#endif
    }

#if DEBUG
    ~TimedLock()
    {
        // If this finalizer runs, someone somewhere failed to
        // call Dispose, which means we've failed to leave
        // a monitor!
        System.Diagnostics.Debug.Fail("Undisposed lock");
    }
#endif

我宁愿抛出一个异常而不是Debug.Fail
Pedro77 '18

17

我们在项目中使用了Red Gate软件的Ants Profiler Pro。它对于所有基于.NET语言的应用程序都非常有效。

我们发现.NET垃圾收集器在清理内存中的对象(应该如此)方面非常“安全”。它将保留对象只是因为我们将来可能会使用它。这意味着我们需要更加注意内存中膨胀的对象数量。最后,我们将所有数据对象转换为“按需扩展”(恰好在请求字段之前),以减少内存开销并提高性能。

编辑:这是对我所说的“按需膨胀”的进一步解释。在数据库的对象模型中,我们使用父对象的属性来公开子对象。例如,如果我们有一些记录以一对一的方式引用了其他“详细”或“查找”记录,那么我们将其结构如下:

class ParentObject
   Private mRelatedObject as New CRelatedObject
   public Readonly property RelatedObject() as CRelatedObject
      get
         mRelatedObject.getWithID(RelatedObjectID)
         return mRelatedObject
      end get
   end property
End class

我们发现,当内存中有很多记录时,上述系统会产生一些实际的内存和性能问题。因此,我们切换到一个系统,其中仅在请求对象时才夸大对象,并且仅在必要时才进行数据库调用:

class ParentObject
   Private mRelatedObject as CRelatedObject
   Public ReadOnly Property RelatedObject() as CRelatedObject
      Get
         If mRelatedObject is Nothing
            mRelatedObject = New CRelatedObject
         End If
         If mRelatedObject.isEmptyObject
            mRelatedObject.getWithID(RelatedObjectID)
         End If
         return mRelatedObject
      end get
   end Property
end class

事实证明,这样做的效率要高得多,因为对象一直保持在内存中,直到需要它们为止(访问Get方法)。它在限制数据库命中方面提供了极大的性能提升,并在内存空间上获得了巨大的收益。


我第二把这个产品。这是我使用过的最好的分析器之一。
Gord

我发现分析器非常适合解决性能问题。但是,内存分析工具非常差。我发现此工具存在泄漏,但是它在帮助我确定泄漏原因方面是垃圾。如果泄漏恰巧是在非托管代码中,则对您完全没有帮助。
斯科特·朗厄姆

好的,新版本5.1更好。更好的方法是帮助您找到泄漏的原因(尽管-ANTS告诉我,仍有一些问题要在下一版本中解决)。虽然仍然不会执行非托管代码,但是如果您不担心非托管代码,那么现在这是一个非常不错的工具。
Scott Langham

7

除非您的应用程序很简单,否则在编写托管代码时仍然需要担心内存。我将建议两件事:首先,通过C#阅读CLR,因为它将帮助您了解.NET中的内存管理。其次,学习使用像CLRProfiler(Microsoft)这样的工具。这可以让您了解导致内存泄漏的原因(例如,您可以查看大对象堆碎片)


是的 CLRPRofiler非常酷。当尝试浏览它为您提供分配的对象的视图时,它的信息可能会爆炸性的,但是一切都在那里。这绝对是一个很好的起点,尤其是免费的。
斯科特·朗厄姆

6

您正在使用非托管代码吗?根据Microsoft的说法,如果您不使用非托管代码,则传统意义上的内存泄漏是不可能的。

但是,可能不会释放应用程序使用的内存,因此应用程序的内存分配可能会在应用程序的整个生命周期中增长。

摘自Microsoft.com如何在公共语言运行库中识别内存泄漏

当您将非托管代码用作应用程序的一部分时,.NET Framework应用程序中可能会发生内存泄漏。此不受管理的代码可能会泄漏内存,并且.NET Framework运行时无法解决该问题。

此外,一个项目可能似乎只出现内存泄漏。如果声明了许多大型对象(例如DataTable对象),然后将它们添加到集合(例如DataSet)中,则会发生这种情况。这些对象拥有的资源可能永远不会释放,并且在整个程序运行期间这些资源都将保持活动状态。这似乎是一个泄漏,但实际上,这只是程序中内存分配方式的一种症状。

为了处理此类问题,可以实现IDisposable。如果您想了解一些处理内存管理的策略,我建议您搜索IDisposable,XNA,内存管理,因为游戏开发人员需要具有更多可预测的垃圾收集,因此必须强制GC来做。

一个常见的错误是不删除订阅对象的事件处理程序。事件处理程序订阅将防止对象被回收。此外,请查看using语句,该语句使您可以为资源的生存期创建有限范围。


5
参见blogs.msdn.com/tess/archive/2006/01/23/…。内存泄漏是否是“传统的”并不重要,它仍然是泄漏。
康斯坦丁

2
我明白你的意思-但是程序对内存的低效率分配和重用与内存泄漏不同。
Timothy Lee Russell,

好的答案,谢谢您记住我事件处理程序可能很危险。
frameworkninja

3
@Timothy Lee Russel:如果无用(2)之后无限制(1)的内存量可以保持同时分配(有根),而系统中没有任何信息和动力可以及时将其无根化,那就是内存泄漏。即使有一天内存可能被释放,如果在此之前积累了足够多的无用的东西来阻塞系统,那也是一个泄漏。(1)大于O(N),N为有用分配量;(2)如果删除对它的引用不会影响程序功能,则它是无用的。
超级猫

2
@Timothy Lee Russel:当一个实体代表另一个实体持有内存时,通常会发生“内存泄漏”模式,希望在不再需要它时会被告知,但后者会在不通知第一个实体的情况下放弃该实体。拥有内存的实体并不需要它,但是无法确定。
超级猫

5

该博客使用windbg和其他工具进行了一些非常精彩的演练,以跟踪所有类型的内存泄漏。优秀的阅读能力可以培养您的技能。


5

我刚刚修复了Windows服务中的内存泄漏。

首先,我尝试了MemProfiler。我发现它真的很难使用,而且一点也不友好。

然后,我使用了JustTrace,它更易于使用,并为您提供了有关未正确放置的对象的更多详细信息。

它使我能够非常轻松地解决内存泄漏问题。


3

如果您观察到的泄漏是由于缓存实施失控造成的,那么在这种情况下,您可能需要考虑使用WeakReference。这可以帮助确保在必要时释放内存。

但是,恕我直言,最好考虑使用量身定制的解决方案-只有您才真正知道将对象放置多长时间,因此,根据情况设计适当的内务处理代码通常是最好的方法。


3

我更喜欢Jetbrains的dotmemory


您可能是唯一的一个:)
HellBaby

我也尝试过 我认为这是一个很好的工具。易于使用,内容丰富。集成到Visual Studio
redeye

在我们的情况下,在对内存泄漏进行故障排除时,Visual Studio Snapshot工具崩溃/没有快照。Dotmemory保持凉爽,并轻松(似乎)轻松地处理了3GB以上的多个快照。
Michael Kargl19年

3

大枪-Windows调试工具

这是一组了不起的工具。您可以使用它分析托管堆和非托管堆,也可以离线进行分析。这对于调试我们的ASP.NET应用程序之一非常方便,该应用程序由于内存过度使用而不断回收。我只需要创建运行在生产服务器上的活动进程的全内存转储,所有分析都在WinDbg中脱机完成。(结果是一些开发人员过度使用了内存中会话存储。)

博客上有很多关于该主题的有用文章:“如果被破坏了……”


2

要记住的最好的事情是跟踪对对象的引用。以挂起对您不再关心的对象的引用结束是非常容易的。如果您不再使用某些东西,请摆脱它。

习惯于使用具有过期期限的缓存提供程序,这样,如果在期望的时间范围内未引用某些内容,则将其取消引用并清除。但是,如果经常访问它,它将在内存中显示。


2

最好的工具之一是使用Windows调试工具,并使用adplus获取进程的内存转储,然后使用windbgsos插件分析进程的内存,线程和调用堆栈。

您也可以使用此方法来确定服务器上的问题,在安装工具之后,共享目录,然后使用(网络使用)从服务器连接到共享,并崩溃或挂起该过程。

然后离线分析。


是的,这很好用,特别是对于更高级的内容或诊断发行软件中无法轻松附加调试器的问题。该博客提供了许多有关如何正确使用这些工具的技巧:blogs.msdn.com/tess
Scott Langham'Oct


0

从Visual Studio 2015开始,请考虑使用现成的“ 内存使用情况”诊断工具 来收集和分析内存使用情况数据。

使用“内存使用情况”工具,您可以为托管和本机内存堆拍摄一个或多个快照,以帮助了解对象类型对内存使用情况的影响。


0

我使用过DotMemory的最好的工具之一。您可以将此工具用作VS中的扩展。运行应用程序后,您可以分析应用程序使用的内存的每个部分(按Object,NameSpace等),并对其进行快照,将其与其他SnapShots进行比较。 点记忆

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.