托管(.net)应用程序中最常见(通常被忽视)的内存泄漏原因是什么?

时间:2009-03-23 10:07:19

标签: .net memory

请任何人都可以推荐快速检查清单/最佳实践指南,以帮助我们避免在.net应用程序中导致内存泄漏的简单(但微妙)错误

当我处于项目的测试阶段时,我发现开始寻找内存泄漏的原因很困难而且非常痛苦。

如果有“经验法则”可以完全指导托管应用程序中的内存泄漏,我恳请您分享您的经验。

感谢。

(我认为托管应用程序被假定为'内存管理'即GC?为什么我们仍然在纯托管代码中发现泄漏?)

11 个答案:

答案 0 :(得分:16)

有许多形式的泄漏:

  • 非托管泄漏(分配非托管代码的代码)
  • 资源泄漏(分配和使用非托管资源的代码,如文件,套接字)
  • 对象的延长寿命
  • 错误理解GC和.NET内存管理的工作原理
  • .NET运行时中的错误

前两个通常由两个不同的代码处理:

  • 在对象上实现IDisposable并在Dispose方法中处理非托管内存/资源
  • 实施终结器,确保在GC找到对象有资格进行收集时取消分配非托管资源

然而,第三个是不同的。

假设您正在使用包含数千个对象的大型列表,总计相当大的内存。如果您对此列表的引用保留的时间超过了您的需要,那么您将看起来像内存泄漏。此外,如果你不断添加到这个列表,以便它定期增加数据,并且旧数据永远不会被重用,你肯定会有内存泄漏。

我经常看到的一个来源是将方法附加到事件处理程序,但是在完成后忘记取消注册它们,慢慢膨胀事件处理程序的大小和代码来执行。

第四,对.NET内存管理工作方式的错误理解可能意味着您要查看流程查看器中的内存使用情况,并注意到您的应用程序在内存使用方面不断增长。如果您有大量可用的内存,GC可能不会经常运行,从而使您对内存的当前使用的描述不正确,而不是映射内存。

第五个,那个更难,我到目前为止只看到.NET中的一个资源管理错误,而且它已经预定在.NET 4.0中进行修复,它是将桌面屏幕复制到.NET映像中。 / p>


编辑:在回复评论中的问题时,如何避免将参考文献保留的时间超过必要时间,那么唯一的方法就是这样做。

让我解释一下。

首先,如果你有一个长时间运行的方法(例如,它可能正在处理磁盘上的文件,或下载某些东西,或类似的东西),并且你在方法的早期使用了对大数据结构的引用,在长时间运行的部分,然后你没有将该数据结构用于该方法的其余部分,然后.NET,在发布版本中(而不是在调试器下运行)足够聪明,知道这个引用,尽管它保存在技术范围内的变量中,有资格进行垃圾回收。垃圾收集器在这方面非常积极。在调试版本中并在调试器下运行时,它将保留方法生命周期的引用,以防您在断点处停止时检查它。

但是,如果引用存储在声明方法的类中的字段引用中,那么它就不那么聪明了,因为无法确定它是否会在以后重用,或者至少非常非常困难。如果不需要这个数据结构,你应该清除你所持有的引用,以便GC稍后再接收它。

答案 1 :(得分:5)

简短的回答是非明显的参考。

添加一些细节:

  • 收集AppDomain之前不收集静态(可能等于进程关闭)
  • 活动(记得取消订阅)
  • 已阻止的终结器(终结器按顺序运行,因此任何阻塞终结器都将阻止收集所有其他可终结对象)。示例包括无法访问STA线程的终结器。
  • 死锁线程永远不会释放根
  • 忘记调用Monitor.Exit()(例如,当使用超时或跨方法时)可能会导致死锁,进而可能导致泄漏

答案 2 :(得分:4)

第一个是附加且永不分离的事件处理程序。

如果事件的订阅者的寿命比事件的生产者长,并且订阅者没有断开事件处理程序,那么触发事件的对象将保持活动状态,因为仍然通过订阅对象引用它。

答案 3 :(得分:4)

回答你的最后两个问题:

根据定义,托管代码中没有内存泄漏。可能发生两种泄漏:

  • 当没有对该对象的引用存活时,将释放对象。如果仍然有对象的引用,则不会释放它。例如,当您丢弃对已注册事件的对象的引用时,如果您不手动取消注册事件处理程序(或使用弱引用),则会发生这种情况,该事件仍将引用该对象,因此它不会被释放虽然你已经没有明显的参考了。
  • 可以泄露非托管资源。通常,非托管资源的包装器实现IDisposable,并在调用Dispose时释放资源。如果您只是丢弃该对象,它将不会释放该资源,从而泄漏它。

所以,有两条经验法则是:

  • 在释放对象时取消注册任何事件处理程序,或使用弱引用(在SO上搜索,在某处解释)。
  • 如果某个类公开了Dispose方法,请调用它。如果仅临时使用该对象,请使用using构造。如果您有成员实现IDisposable,请自己实现IDisposable并在Dispose中调用成员的Dispose。

答案 4 :(得分:4)

您可以采取的有效管理记忆的众多好事之一。

尽可能在DataSet或DataTable对象上调用处理任何实现IDisposable的对象。

最好还是在这些对象上使用using {}结构。

答案 5 :(得分:4)

内存泄漏可能来自多种原因。

  • 您注册了一个活动,但忘了取消注册该活动。

  • 文件/连接已打开,但未正确关闭。

  • Dispose()调用未正确实现。

  • 以某种方式绕过了Dispose()调用;例如,在两者之间遇到异常。

  • 您遇到了死锁(这可能导致不释放根对象)。

  • 终结器线程被阻塞;例如,COM对象的STA线程不可用。

  • 不受管代码泄漏。

  • 对象的生存期太长。

  • 有一个循环引用。

  • 泄漏可能来自.NET运行时环境(在极少数情况下)。

  • 测试框架中也会出现泄漏。 (在这种情况下, 是测试泄漏,而不是开发环境泄漏,并且测试 工程师负责解决此问题。)

答案 6 :(得分:3)

在我看来,托管应用程序中内存泄漏的首要原因是错误地认为您有内存泄漏,而实际上您没有正确测量。请务必使用内存分析器来准确测量您的内存泄漏类型。你可能会发现还有其他问题。

但是当泄漏是真实的时,主要原因是引用物体,当它们不是真正需要时保持这些物体存活。为几乎所有实现IDisposable的对象实现使用语句是一个好的开始。我所知道的唯一例外是WCF代理类,应该使用try / catch / finally访问,而不是使用语句。

答案 7 :(得分:2)

我可以开始建议人们应该始终了解IDisposable个对象以及如何正确处理它们。此外,请谨慎使用您的应用中的状态仅在绝对必要时抓取对象。如果对象A在您的应用中长时间存活,请始终尝试不要在A和其他对象之间建立依赖关系。

答案 8 :(得分:1)

值类中的循环引用(通常在应用程序的模型层中)也可以轻松隐藏它们的泄漏。从理论上讲,不要这样做,在实践中,每当你需要时都要注意:)

答案 9 :(得分:1)

检查使用后内存中不需要的静态变量,检查事件处理引用,阻止其他终结器被收集的任何终结器,以及是否有任何未经管理的引用任何DLL或COM +对象

答案 10 :(得分:1)

以下是内存泄漏的主要原因。

  1. 保持对托管对象的引用(事件处理程序的经典案例未发布。)
  2. 未能释放非托管资源
  3. 未能处理绘图对象
  4. 我注意到计时器也会导致内存泄漏。如果您使用任何计时器,请将其丢弃。
  5. 请继续阅读http://blogs.msdn.com/b/davidklinems/archive/2005/11/16/three-common-causes-of-memory-leaks-in-managed-applications.aspx