哪些策略和工具对于在.NET 中查找内存泄漏是有用的?

我写了10年的 C + + 。我遇到了内存问题,但只要付出合理的努力就可以解决。

过去几年我一直在写 C # 。我发现我还是有很多记忆问题。由于不确定性,它们很难诊断和修复,而且因为 C # 的哲学是,当你非常确定要做的时候,你不应该担心这些事情。

我发现的一个特殊问题是,我必须显式地释放和清除代码中的所有内容。如果我不这样做,那么内存分析器就不会真正起作用,因为有太多的障碍,你无法在他们试图向你展示的所有数据中找到漏洞。我不知道我是否有错误的想法,或者我的工具不是最好的。

什么样的策略和工具对于处理.NET 中的内存泄漏是有用的?

115115 次浏览

当我怀疑内存泄漏时,我会使用 Scitech 的 MemProfiler

到目前为止,我发现它是非常可靠和强大的。至少有一次它救了我的命。

GC 在。NET IMO,但是就像任何其他语言或平台一样,如果你写了错误的代码,坏事情就会发生。

在编写托管代码时,您仍然需要担心内存问题,除非您的应用程序是琐碎的。我将提出两点建议: 首先,阅读 CLR via C # ,因为它将帮助您理解。NET.其次,学会使用像 CLRProfiler(微软)这样的工具。这可以让您了解是什么导致了内存泄漏(例如,您可以查看大型对象堆碎片)

我们已经在我们的项目中使用了 蚂蚁分析专家 by Red Gate 软件,它对所有基于.NET 语言的应用程序都非常有效。

我们发现。NET 垃圾收集器在清理内存对象时是非常“安全”的(应该是这样)。它将保持对象周围,只是因为我们 也许吧正在使用它在未来的某个时候。这意味着我们需要更加注意在内存中膨胀的对象的数量。最后,我们将所有数据对象转换为“按需膨胀”(就在请求字段之前) ,以减少内存开销并提高性能。

编辑: 这里是对我所说的“按需膨胀”的进一步解释在数据库的对象模型中,我们使用父对象的 Properties 来公开子对象。例如,如果我们有一些记录在一对一的基础上引用了一些其他的“细节”或“查找”记录,我们会这样构造它:

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 方法被访问)。它在限制数据库命中率方面提供了非常大的性能提升,并在内存空间方面获得了巨大的增益。

要记住的最好的事情是跟踪对对象的引用。最终很容易挂起对您不再关心的对象的引用。 如果你不打算再用什么东西了,就把它扔掉。

习惯于使用具有滑动过期的缓存提供程序,这样,如果某些内容没有被引用到所需的时间窗口,它就会被解引用并被清除。但是如果访问量很大,它会在内存中显示。

最好的工具之一是使用 Windows 调试工具,使用 加上获取进程的内存转储,然后使用 胡说八道求救插件分析进程内存、线程和调用堆栈。

您也可以使用这种方法来识别服务器上的问题,在安装工具之后,共享目录,然后使用(网络使用)从服务器连接到共享,并对进程进行崩溃或挂起转储。

然后离线分析。

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

然而,恕我直言,最好考虑一个定制的解决方案-只有你真正知道你需要多长时间保持周围的对象,所以设计适当的内务管理代码为您的情况通常是最好的方法。

至于忘记处理的问题,可以试试 这篇博文中描述的解决方案:

    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

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

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

来自 如何识别 Microsoft.com 通用语言运行库中的内存泄漏

内存泄漏可能发生在.NET 中 框架应用程序时使用 非托管代码作为 此非托管代码可以 内存泄漏和.NETFramework 运行库无法解决这个问题。

此外,一个项目可能只 似乎有内存泄漏 条件可能会发生,如果许多大 对象(例如 DataTable 对象) 声明,然后添加到 集合(例如 DataSet) 这些对象拥有的资源可能 永远不会被释放 留下活口 这似乎是一个 泄漏,但实际上它只是一个 记忆的症状 被分配到这个项目中。

为了处理这种类型的问题,您可以实现 一次性手机。如果您想了解一些处理内存管理的策略,我建议您搜索 一次性,XNA,内存管理,因为游戏开发人员需要有更多可预测的垃圾收集,所以必须强制 GC 完成它的工作。

一个常见的错误是没有删除订阅对象的事件处理程序。事件处理程序订阅将防止对象被回收。另外,看一下 使用语句,它允许您为资源的生命周期创建一个有限的范围。

大炮 -Windows 调试工具

这些工具真是太棒了。可以使用它分析托管堆和非托管堆,也可以脱机执行。这对于调试我们的一个 ASP.NET 应用程序非常方便,这个应用程序由于内存过度使用而不断循环。我只需要创建一个生产服务器上运行的生活进程的完整内存转储,所有的分析都是在 WinDbg 中离线完成的。(事实证明,一些开发人员过度使用了内存中的会话存储。)

博客上有很多关于这个主题的有用的文章。

这个 blog 使用 windbg 和其他工具来跟踪所有类型的内存泄漏,有一些非常棒的演练。优秀的阅读,以发展你的技能。

在一次修复托管应用程序之后,我遇到了同样的问题,比如如何验证我的应用程序在下一次修改之后不会有同样的内存泄漏,所以我写了一些类似对象发布验证框架的东西,请看一下 NuGet 软件包 Objectrelease 验证。您可以在这里找到一个样本 https://github.com/outcoldman/OutcoldSolutions-ObjectReleaseVerification-Sample,以及关于这个样本 http://outcoldman.com/en/blog/show/322的信息

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

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

然后,我使用了更容易使用的 只是追踪,并提供了关于没有正确处置的对象的更多细节。

它让我很容易地解决了内存泄漏问题。

我更喜欢 Jetbrains 的 Dotmemory

从 VisualStudio2015考虑使用开箱即用的 记忆使用诊断工具 来收集和分析内存使用数据。

“内存使用情况”工具允许您对托管和本机内存堆进行一个或多个快照,以帮助理解对象类型对内存使用的影响。

这是我使用过的最好的工具之一你可以把这个工具作为 VS 的扩展在运行你的应用程序之后你可以分析你的应用程序使用的内存的每一部分(通过对象,名称空间,等等)并且拍一些快照,比较它和其他的快照。 DotMemory