内存泄漏在C#

是否有可能在托pipe系统泄漏内存,当你确保所有句柄,实现IDispose东西都处置?

会不会有一些变数被遗漏的情况呢?

事件处理程序是一个非常明显的内存泄漏的常见来源。 如果你从object2订阅object1上的一个事件,然后做object2.Dispose()并假装它不存在(并从你的代码中删除所有的引用),在object1的事件中有一个隐式引用,它将阻止object2垃圾收集。

 MyType object2 = new MyType(); // ... object1.SomeEvent += object2.myEventHandler; // ... // Should call this // object1.SomeEvent -= object2.myEventHandler; object2.Dispose(); 

这是泄露的常见情况 – 忘记轻松取消订阅事件。 当然,如果object1被收集,object2也会被收集,但是直到那时才会收集。

我不认为C ++风格的内存泄漏是可能的。 垃圾收集器应该占这些。 即使对象不再被使用,也可以创build一个聚合对象引用的静态对象。 像这样的东西:

 public static class SomethingFactory { private static List<Something> listOfSomethings = new List<Something>(); public static Something CreateSomething() { var something = new Something(); listOfSomethings.Add(something); return something; } } 

这是一个明显的愚蠢的例子,但它将是一个托pipe运行时内存泄漏的等价物。

正如其他人所指出的,只要内存pipe理器中没有实际的错误,那些不使用非托pipe资源的类就不会泄漏内存。

你在.NET中看到的不是内存泄漏,而是永远不会被丢弃的对象。 只要垃圾收集器可以在对象图上find对象,对象就不会被丢弃。 所以如果任何地方的活物都有对象的引用,它就不会被丢弃。

事件注册是实现这一目标的好方法。 如果一个对象注册了一个事件,那么无论它注册了哪一个对象都有一个引用,并且即使你消除了对该对象的所有其他引用,直到它注销(或者注册的对象变得不可访问),它也将保持活动状态。

所以你不得不注意那些在你不知情的情况下注册静态事件的对象。 例如, ToolStrip一个很好的function是,如果你改变你的显示主题,它会自动重新显示在新的主题。 它通过注册静态SystemEvents.UserPreferenceChanged事件来完成这个漂亮的function。 当您更改Windows主题时,会引发事件,并且正在侦听事件的所有ToolStrip对象都会收到通知,说明有新的主题。

好吧,所以假设你决定扔掉你的表单上的ToolStrip

 private void DiscardMyToolstrip() { Controls.Remove("MyToolStrip"); } 

你现在有一个永远不会死的ToolStrip 。 即使它不在你的表单上,每当用户改变主题时,Windows都会尽职尽责地告诉其他不存在的ToolStrip 。 每次垃圾收集器运行时,都会认为“我不能丢弃该对象, UserPreferenceChanged事件正在使用它”。

这不是内存泄漏。 但它可能是。

像这样的东西使内存分析器非常宝贵。 运行一个内存分析器,然后你会说:“这很奇怪,堆上似乎有一万个ToolStrip对象,即使我的表单上只有一个,这是怎么发生的?

哦,如果你想知道为什么有些人认为属性设置器是邪恶的:要获取ToolStripUserPreferenceChanged事件取消注册,请将其Visible属性设置为false

代表可能会导致不直观的内存泄漏。

无论何时从实例方法创build委托,对该实例的引用都被存储在“委托”中。

此外,如果将多个委托组合到一个多播委托中,那么只要该多播委托正在某处使用,就会有大量的对象被引用,从而不会被垃圾收集。

如果您正在开发一个WinForms应用程序,一个微妙的“泄漏”是Control.AllowDrop属性(用于启用拖放)。 如果AllowDrop设置为“true”,那么CLR将仍然保持在您的控件上,通过System.Windows.Forms.DropTarget 。 为了解决这个问题,确保你的ControlAllowDrop属性在你不再需要的时候被设置为false ,而CLR将负责其余的部分。

正如已经提到的那样,保持引用会导致随着时间的推移内存使用量的增加 进入这种情况的一个简单的方法是事件。 如果你有一个长时间的活着的对象,而其他对象正在侦听某个事件,那么如果这些侦听器不会被移除,那么这个长期存在的对象上的事件将会让这些其他实例在不再需要的时候长时间地活着。

.NET应用程序内存泄漏的唯一原因是对象仍在被引用,尽pipe它们的寿命已经结束。 因此,垃圾收集器不能收集它们。 他们成为长寿命的对象。

我发现订阅事件很容易导致泄漏,而不会在对象的生命结束时取消订阅。

您可能会发现我的新文章很有用: 如何检测和避免.NET应用程序中的内存和资源泄漏

reflection散发是另一个潜在的泄漏源,例如内置对象反序列化器和精美的SOAP / XML客户端。 至less在框架的早期版本中,从属AppDomain中生成的代码从未卸载…

在托pipe代码中不能泄漏内存是一个神话。 当然,这比在非托pipeC ++中要困难得多,但是有一百万种方法可以做到这一点。 静态对象持有引用,不必要的引用,caching等。如果你正在做的事情是“正确”的方式,你的许多对象将不会被垃圾收集,直到晚于必要的时间,这也是一种内存泄漏在我看来,以一种实际而不是理论的方式。

幸运的是,有些工具可以帮助你。 我使用微软的CLR Profiler很多 – 它不是迄今为止用户友好的工具,但它绝对是非常有用的,而且是免费的。

一旦对象的所有引用都没有了,垃圾收集器就会释放下一个对象。 我不会说这是不可能泄漏的内存,但它是相当困难的,为了泄漏你不得不有一个对象坐在一个没有意识到它的参考。

例如,如果您将对象实例化为列表,然后在完成时忘记将它们从列表中删除,并忘记处理它们。

如果非托pipe资源得不到正确清理,可能会发生泄漏。 实现IDisposable的类可能会泄漏。

但是,常规的对象引用不需要像低级语言那样需要显式的内存pipe理。

不是真正的内存泄漏,但是当使用大对象(如果我没有记错的话,大于64K)很容易耗尽内存。 它们存储在LOH中,不会被碎片整理。 因此,使用这些大对象并释放它们释放了LOH上的内存,但是.NET运行时不再使用这个可用内存。 所以你可以很容易地用LOH上的几个大件物品把LOH上的空间用完。 微软已经知道这个问题,但是现在我记得这个解决scheme正在计划中。

唯一的漏洞(除了可能存在的运行时错误,尽pipe不是由于垃圾收集造成的)很可能是本地资源。 如果你将P / Invoke打开到一个本地库,它打开文件句柄或套接字连接,或者代表你的托pipe应用程序,并且你永远不会显式closures它们(也不要在处理器或析构函数/终结器中处理它们),你可以有内存或资源泄漏,因为运行时无法为您自动pipe理所有这些。

如果你坚持纯粹的pipe理资源,但是,你应该没问题。 如果您遇到任何forms的内存泄漏,而不调用本地代码,那么这是一个错误。

尽pipe框架中的某些东西有可能泄漏,但是有可能是因为某些东西没有被正确处理,或者某些东西阻止了GC的处置,IIS将成为这个的主要候选者。

只要记住,在.NET中并不是所有的东西都是完全托pipe的代码,COM互操作,像文件stream,数据库请求,图像等文件。

我们前一个问题(IIS 6上的.net 2.0)的问题是,我们将创build一个映像然后将其丢弃,但IIS不会释放一段时间的内存。

在我上一份工作中,我们使用了第三方.NET SQLite库,它像筛子一样泄露。

我们在奇怪的情况下进行了大量的快速数据插入,每次都必须打开和closures数据库连接。 第三方lib做了一些与我们手动做的相同的连接,并没有logging它。 它也举行了我们从未find的地方的参考。 结果是打开的连接数量是原来的两倍,只有1/2的closures。 而且由于引用被保留,我们有一个内存泄漏。

这显然不同于经典的C / C ++内存泄漏,但是对于所有意图和目的来说,这是我们的一个目标。

如果它被认为是内存泄漏,那么也可以用这种代码来实现:

 public class A { B b; public A(B b) { this.b = b; } ~A() { b = new B(); } } public class B { A a; public B() { this.a = new A(this); } ~B() { a = new A(this); } } class Program { static void Main(string[] args) { { B[] toBeLost = new B[100000000]; foreach (var c in toBeLost) { toBeLost.ToString(); //to make JIT compiler run the instantiation above } } Console.ReadLine(); } } 

小函数有助于避免“内存泄漏”。 因为垃圾收集器在函数结束时释放局部variables。 如果函数很大,并占用大量内存,则必须释放占用大量内存且不再需要的局部variables。 类似的全局variables(数组,列表)也是不好的。

在创build图像时,我在C#中遇到了内存泄漏,而不处理它们。 这有点奇怪。 人们说,你必须在每个拥有它的对象上调用.Dispose()。 但graphicsC#函数的文档并不总是提到这一点,例如函数GetThumbnailImage()。 C#编译器应该警告你这个我想。