如何避免内存泄漏?

时间:2009-04-27 17:25:23

标签: c# memory-leaks

我可以使用哪些提示来避免应用程序中的内存泄漏?我有什么可以留意的陷阱或陷阱吗?

18 个答案:

答案 0 :(得分:21)

对IDisposable对象调用Dispose或使用using子句。这应该照顾我能想到的大部分泄漏。

答案 1 :(得分:12)

注意您删除了您使用的所有事件处理程序。在.NET中,它们是泄漏内存的最常见原因。

答案 2 :(得分:7)

正如ocdecio所提到的,请务必在Idisposable对象上调用Dispose,并记住在完成对象后删除事件处理程序。在构建使用非托管资源的类时,请务必实现Idisposable,以便用户知道有一些关键资源需要处理。

此外,即使垃圾收集对您来说有相当多的工作,您也应该摆脱对您所做的对象的引用。否则他们仍然会有一个根,他们将不会被GC。

答案 3 :(得分:6)

在这些情况下,不要低估工具的帮助性。 .NET内存分析器现在相当成熟和强大,如果你有一个复杂的应用程序,你认为应该释放的对象仍被其他东西作为参考,那么精确定位该引用的能力是非常宝贵的。

我刚刚完成了内存泄漏,其中一个WPF标签页托管了一个Windows窗体控件(因为这些标签包含大量数据,您可以随意打开和关闭它们,只需等待GC清除内存关闭不是一个选择)。我使用YourKit分析器在打开选项卡之前获取内存的快照,打开选项卡,再次关闭它并拍摄另一个快照。在探查器内部,您可以在视觉上比较两个快照,并查看哪些对象在该过程中幸存,并将其引用关注回GC根。我没有其他探查器的经验,但我知道如果YourKit无法满足您的需求,那里有一些。


编辑添加:

好的,这不是避免内存泄漏,它正在修复它们。但我会把它留在这里,因为我认为它是有用的信息,我认为没有足够的.NET开发人员了解这些工具。

答案 4 :(得分:5)

我知道有些人会建议垃圾收集作为解决方案。但是有很多情况下垃圾收集不能提供您期望的结果。很容易最终保持一个杂散的引用,阻止整个内存链被释放。 Read关于这是如何破坏DARPA大挑战赛的入场券。您可以认为这些不是内存泄漏,但如果程序希望释放内存,那么它仍然是一个问题。就像在C编程中一样,几个月后你就会知道如何确保不留下不需要的引用。

答案 5 :(得分:5)

内存泄漏是错误,所以一般来说 - 这个问题可以像“如何编写没有错误”一样回答?从长远来看 - 不可能没有错误,但你可以限制在发布的代码中使用它们的机会。

首先关注开发的代码质量并遵循其他人提到的指南。

  1. 简单是金色的 - 代码越简单 - 漏洞或泄漏的机会越少
  2. 使用非托管资源时要小心 - Windows句柄,数据库连接,GDI对象等
  3. 用于IDisposables
  4. 为承载非托管资源的类实现IDisposable
  5. 确保删除对未使用对象的任何引用 - 包括棘手的事件处理程序
  6. 除此之外 - 实现测试以查看内存是否泄漏 - 单元,并发,压力和负载测试在这方面最有帮助。通过检查指标(perf计数器)查看内存是否泄漏。您还可以记录对象创建和破坏,并在测试结束时分析日志。

答案 6 :(得分:3)

我遇到了一个问题,其中一个对象(Ping)实现了Dispose()两次,通过实现IDisposable接口并同时继承它。继承的方法什么也没做,因此在调用Dispose()时你必须将对象强制转换为IDisposable,否则它会泄漏内存。这是一个更详细的post I wrote a few years ago

答案 7 :(得分:3)

  1. 在使用构造中包裹任何可丢弃的东西。
  2. 避免使用COM对象。
  3. 检查是否正确删除了所有事件处理程序。
  4. 检查是否正确删除了所有数据绑定。
  5. 保持简单
  6. 如果您的应用程序逻辑变得不必要复杂,您可能会开始以内存泄漏结束。如果你保持你的类很小并遵循一般的编码实践,你可能不会遇到托管代码的任何内存泄漏。这是可能的,但不像它使用的那样可能。

    如果您怀疑内存泄漏,请使用分析器查看是否有任何对象被保留的时间超过了所需的时间。

    我最后一次遇到严重的内存泄漏是.NET 1.1,结果发现框架中有一个错误。

答案 8 :(得分:2)

基本了解garbage collector如何工作将有助于避免滥用记忆。例如,如果您保留对不再需要的对象的引用,则gc将无法收集它。

同样,如果您要存储用户输入的数据,或者随着时间的推移而添加的数据,您应该考虑某种限制,以便您的内存使用量不会无限增长。

答案 9 :(得分:2)

我在.NET中遇到的大多数内存泄漏都与使用COM对象有关,而不是正确释放它们。只要我看到对COM对象的引用,我就认为“内存泄漏”。

答案 10 :(得分:2)

最常见的内存没有被GC破坏的情况是当你的事件处理程序没有被正确解开时。如果需要,可以在Dispose()中取消挂钩。

我更详细地描述了这个问题,我有办法编写测试来确定你是否泄漏了对象here

答案 11 :(得分:2)

正如其他人所说的那样调用Dispose()(或使用using语句),但另外考虑类是否使用资源,如果是,则实现IDisposeable。这通常是我的代码中的问题是我有一个成员的类,直到GC才清理它。

答案 12 :(得分:1)

您可能会发现此链接很有用: Solving the Problem with Events: Weak Event Handlers

答案 13 :(得分:1)

首先,让我分享我对内存泄漏的严格理解。我对内存泄漏的定义是当你有分配的内存而不再引用它时,所以不可能释放内存。话虽如此,在.net对象中存在内存泄漏是不可能的(我的意思是生活在托管堆中的CTS类型的实例)。未引用的内存正是GC要求释放的内容。

话虽如此,人们对内存泄漏的理解可能会更加疏忽。如果你认为内存泄漏是所使用的内存不受控制的增长,那么这很容易。只是大量滥用静态变量,主要是那些引用巨大列表的变量。如果你保持引用这些对象,GC将永远不会清理它们,将它们推广到更高代,并使它们更难收集。即使这不是严格意义上的内存泄漏,但在一天结束时它可能会导致类似的症状。尝试检测此类“泄漏”的好方法是使用CLR Profiler

“泄漏”的另一个来源是如前所述通过不当使用事件处理程序。每当对象A在对象B上用事件注册其实例方法之一时,对象B就会结束对对象A的间接引用,这意味着当B处于活动状态时,A将保持活动状态。但请注意,这里没有循环。只要B或A都没有任何根引用,无论它们有多少交叉引用,它们最终都会被收集。

最后,人们实际上可以在.net中引发内存泄漏,但在谈论托管内存时从不(至少在理论上),因为GC在清除它时做得很好。如果您的任何对象维护对非托管内存的引用(例如通过interop),则需要显式清除该内存。事实上,如果不这样做会导致内存泄漏。即使我从未经历过这样的场景,至少在理论上这可能会发生。如前所述,持有此类引用的对象应该实现IDiposable以清除该内存,并且它们的使用应该保证为此目的调用Dispose,主要是通过使用using子句。请注意,Dispose不会释放对象的内存,但只会要求对象释放它所指的任何非托管内存。

一种特殊的非托管内存是互操作方案中使用的COM对象所需的内存。这些对象可以通过Runtime Callable Wrappers,RCWs为朋友访问,但没有Dispose。 “使用”不起作用。释放底层COM对象的方法是通过静态方法:

System.Runtime.InteropServices.Marshal.ReleaseComObject(object);

由于“using”只是在finally块中调用IDisposable.Dispose()的语法糖,因此不能与RCW一起使用,因此不要忘记将调用放在最终块中的ReleaseComObject(object)中,这样你确保它真的被称为。

答案 14 :(得分:1)

如果由于某种原因任何一个终结器阻塞,实现终结器的类型可能会泄漏。我看到终结器由于锁定和线程公寓问题而阻塞。

由于在各自的终结器运行之前不会收集具有终结器的类型的实例,因此单个阻塞终结器将阻止任何其他可终结的对象等待收集。

答案 15 :(得分:1)

这是托管代码,即c#,所以你必须努力泄漏内存:P

试试google: http://www.google.com/search?hl=en&client=firefox-a&rls=org.mozilla%3Aen-US%3Aofficial&hs=Mbp&q=c%23+memory+leaks&btnG=Search

答案 16 :(得分:0)

使用“using”关键字自动调用IDisposable对象的Dispose()方法。
对于任何COM互操作,您必须手动释放所有资源。

答案 17 :(得分:0)