20

在 .NET 中发生内存泄漏的所有可能方式有哪些?

我知道两个:

  1. 未正确取消注册Event Handlers/Delegates
  2. 不在 Windows 窗体中释放动态子控件:

例子:

// Causes Leaks  
Label label = new Label();  
this.Controls.Add(label);  
this.Controls.Remove(label);  

// Correct Code  
Label label = new Label();  
this.Controls.Add(label);  
this.Controls.Remove(label);  
label.Dispose();

更新:这个想法是列出不太明显的常见陷阱(例如上面的)。通常的想法是,由于垃圾收集器,内存泄漏不是一个大问题。不像以前在 C++ 中那样。


很棒的讨论家伙,但让我澄清一下……根据定义,如果在 .NET 中没有对对象的引用,它将在某个时候被垃圾收集。所以这不是诱发内存泄漏的方法。

在托管环境中,如果您无意中引用了您不知道的任何对象(因此我的问题中有两个示例),我会认为这是内存泄漏。

那么,发生这种内存泄漏的各种可能方式是什么?

4

14 回答 14

21

这并不会真正导致泄漏,它只会为 GC 做更多的工作:

// slows GC
Label label = new Label();  
this.Controls.Add(label);  
this.Controls.Remove(label);  

// better  
Label label = new Label();  
this.Controls.Add(label);  
this.Controls.Remove(label);  
label.Dispose();

// best
using( Label label = new Label() )
{ 
    this.Controls.Add(label);  
    this.Controls.Remove(label);  
}

在像 .Net 这样的托管环境中,像这样放置一次性组件从来都不是什么大问题——这是托管意味着的很大一部分。

当然,您会减慢应用程序的速度。但你不会为其他任何事情留下一团糟。

于 2008-08-21T16:21:22.623 回答
14

没有办法提供一个全面的清单……这很像问“你怎么会弄湿?”

也就是说,请确保对实现 IDisposable 的所有内容都调用 Dispose(),并确保对消耗任何类型的非托管资源的任何类型都实现 IDisposable。

时不时地,在你的代码库上运行类似 FxCop 的东西来帮助你执行这个规则——你会惊讶于一些一次性对象被埋在应用程序框架中的深度。

于 2008-08-21T16:13:22.720 回答
14

直接设置GridControl.DataSource属性,而不使用 BindingSource 类 ( http://msdn.microsoft.com/en-us/library/system.windows.forms.bindingsource.aspx ) 的实例。

这导致我的应用程序出现泄漏,我花了很长时间才使用分析器进行追踪,最终我发现了微软回应的这个错误报告:http ://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID= 92260

有趣的是,在 BindingSource 类的文档中,Microsoft 试图将其作为一个经过深思熟虑的合法类传递出去,但我认为他们创建它只是为了解决有关货币管理器和将数据绑定到网格控件的基本泄漏问题。

当心这个,我敢打赌那里绝对有很多泄漏的应用程序,因为这个!

于 2008-11-25T20:34:06.973 回答
5

阻塞终结器线程。在终结器线程被解除阻塞之前,不会对其他对象进行垃圾收集。因此,使用的内存量将不断增长。

进一步阅读:http ://dotnetdebug.ne​​t/2005/06/22/blocked-finalizer-thread/

于 2008-08-23T08:17:01.867 回答
4

Finalize(或来自 Finaliser 的 Dispose 调用)方法中的异常会阻止非托管资源被正确释放。一个常见的原因是程序员假设将处置哪些订单对象并尝试释放已处置的对等对象,从而导致异常,并且未调用 Finalize 方法中的 Finalise/Dispose 的其余部分。

于 2008-08-21T16:38:56.030 回答
3

我有 4 个额外的项目要添加到这个讨论中:

  1. 终止已创建 UI 控件但未正确准备此类事件的线程 (Thread.Abort()) 可能会导致内存被预期使用。

  2. 通过 Pinvoke 访问非托管资源而不清理它们可能会导致内存泄漏。

  3. 修改大字符串对象。不一定是内存泄漏,一旦超出范围,GC 会处理它,但是,在性能方面,如果经常修改大字符串,您的系统可能会受到影响,因为您不能真正依赖 GC 来确保程序的足迹是最小。

  4. 经常创建 GDI 对象以执行自定义绘图。如果经常执行 GDI 工作,请重用单个 gdi 对象。

于 2010-07-27T18:25:47.947 回答
2

您是在谈论意外的内存使用还是实际泄漏?您列出的两种情况并不完全是泄漏;它们是物体停留时间超过预期的情况。

换句话说,它们是那些称它们为内存泄漏的人不知道或忘记的引用。

编辑:或者它们是垃圾收集器或非托管代码中的实际错误。

编辑 2:考虑这个问题的另一种方法是始终确保对对象的外部引用得到适当的释放。外部意味着您无法控制的代码。发生这种情况的任何情况都是您可以“泄漏”内存的情况。

于 2008-08-21T16:18:12.397 回答
2

每次调用 IDisposable 是最容易开始的地方,而且绝对是获取代码库中所有低悬的内存泄漏果实的有效方法。然而,这并不总是足够的。例如,了解托管代码在运行时生成的方式和时间也很重要,一旦程序集加载到应用程序域中,它们就永远不会被卸载,这会增加应用程序的占用空间。

于 2008-08-21T16:19:17.450 回答
2

为了防止 .NET 内存泄漏:

1) 每当创建具有“IDisposable”接口的对象时,都使用“using”构造(或“try-finally 构造”)。

2) 如果类创建线程或将对象添加到静态或长期存在的集合,则将类设为“IDisposable”。请记住,C#“事件”是一个集合。

这是一篇关于防止内存泄漏的提示的简短文章。

于 2010-06-25T20:20:29.487 回答
1
  1. 保留对不再需要的对象的引用。

关于其他评论 - 确保调用 Dispose 的一种方法是在代码结构允许时使用 using... 。

于 2009-11-13T01:20:48.917 回答
1

对我来说真正出乎意料的一件事是:

Region oldClip = graphics.Clip;
using (Region newClip = new Region(...))
{
    graphics.Clip = newClip;
    // draw something
    graphics.Clip = oldClip;
}

内存泄漏在哪里?对,你也应该处理掉oldClip!因为Graphics.Clip是每次调用 getter 时返回一个新的一次性对象的罕见属性之一。

于 2010-06-25T20:30:16.290 回答
1

Tess Fernandez 有很多关于查找和调试内存泄漏的博客文章。 实验室 6 实验室 7

于 2010-07-28T13:46:03.563 回答
0

许多可能导致非托管语言中的内存泄漏的事情仍然可能导致托管语言中的内存泄漏。例如,糟糕的缓存策略会导致内存泄漏。

但正如格雷格和丹尼所说,没有完整的清单。任何可能导致在其有用生命周期后保留内存的东西都可能导致泄漏。

于 2008-08-21T16:18:28.387 回答
0

死锁的线程永远不会释放根。显然,您可以争辩说僵局带来了更大的问题。

死锁的终结器线程将阻止所有剩余的终结器运行,从而阻止所有可终结的对象被回收(因为它们仍然被 freachable 列表作为根)。

在多 CPU 机器上,您可以比终结器线程运行终结器更快地创建可终结对象。只要这种情况持续下去,您就会“泄漏”内存。这可能不太可能在野外发生,但很容易复制。

大型对象堆未压缩,因此您可能会通过碎片泄漏内存。

有许多对象必须手动释放。例如,没有租约和程序集的远程处理对象(必须卸载 AppDomain)。

于 2008-11-25T20:08:21.643 回答