托管代码中是否可能存在内存泄漏? (特别是C#3.0)

例如,如果我有一个分层数据结构:

class Node { public List children; } 

然后在其中一个父母中填充到很多级别:

 myNode.children.Clear(); 

这将清除所有对直系孩子的提及 – 但那些直系孩子所引用的所有大孩子,大孙子等等呢? C#聪明到知道它们不再需要它们会被垃圾收集吗?

我已阅读使用WPF数据绑定而没有实现接口INotifyChanged可能导致内存泄漏: http : //blogs.msdn.com/b/micmcd/archive/2008/03/07/avoiding-a-wpf-memory-leak-with-数据绑定-black-magic.aspx ,在托管环境中这怎么可能?

是的,垃圾收集器会发现孙子等是垃圾。 基本上,如果没有办法到达一个对象,它被认为是垃圾并且有资格收集。

至于托管代码中内存“泄漏”的可能性 – 通常情况下,如果您最终得到一个可通过对象引用访问的对象,但是您无法通过API最终“清除”这些引用。

在你引用的博客文章中就是这种情况:

WPF检查是否存在实现INotifyProperyChanged的问题。 如果存在对未实现此接口的数据绑定,则它在全局表中创建记录。 该记录未被清除,因为WPF无法检查何时不再需要该DB记录。

因此,这个全局表维护引用,并且您无法指示可以清除表中的项。

垃圾收集器只收集不再使用的对象 – 内存泄漏是由仍然持有对象引用的对象引起的,即使它们不应该。

在你的情况下,如果一个大孩子被另一个对象使用,那么.Clear会将它从节点列表中删除,但垃圾收集器不会收集它。 它会收集所有其他的大孩子。

例:

 class Foo { public Node SomeProperty {get; set;} public void SomeFunction(){ var node = new Node { children = new List() }; var childNode = new Node(); var childNode2 = new Node(); node.children.Add(childNode); node.children.Add(childNode2); SomeProperty = childNode2; node.children.Clear(); // childNode will be garbage collected // childNode2 is still used by SomeProperty, // so it won't be garbage collected until SomeProperty or the instance // of Foo is no longer used. } } 

C#并不关心。 执行GC是CLR的工作。

GC从已知的根对象(静态字段,局部变量,…)开始,并遍历引用,直到找到所有可到达的对象。 可以收集所有其他对象(不包括一些与终结器相关的东西)。

因此,如果子引用实际上是对这些对象的唯一引用,那么也将收集大孩子。 但是如果一些活着的外部对象仍然具有对您的一个节点的引用,则该节点和它引用的所有其他对象将保持活动状态。


托管内存泄漏是由保持对象存活的引用引起的。

例如,当使用数据库时,GUI具有对象的引用,使它们保持活动状态。

类似地,订阅事件会使与事件处理程序关联的对象保持活动状态。 所以有时事件使用弱引用来避免这个问题。

另外,如果使用unsafe关键字,也可以在.net中获取内存泄漏。 如果你以与c ++等相同的方式使用指针,并且不小心确保你没有“松散”指针引用,那么GC将无法收集它。

不安全块的例子;

 unsafe { int * ptr1, ptr2; ptr1 = &var1; ptr2 = ptr1; *ptr2 = 20; } 

当然,C#特别缺少每个人都讨论过的其他参考分配的东西,如果你有一个包装原生资源的类但它从未被丢弃(或者你失去对它的引用),你可以创建一个泄漏。

以下是Image类的示例:

 public static void MemLeak() { var src = @"C:\users\devshorts\desktop\bigImage.jpg"; Image image1 = null; foreach (var i in Enumerable.Range(0, 10)) { image1 = Image.FromFile(src); } image1.Dispose(); Console.ReadLine(); } 

Image是一次性的,所以因为我在最后处理图像时不应该有泄漏吗? 实际上,每次使用新图像覆盖引用这一事实意味着您无法处理旧图像引用所持有的基础GDI +资源。 这将引入内存泄漏。

由于gc没有为你调用dispose而Image类没有覆盖Finalize方法(并在那里调用Dispose),那么你自己就是泄密。

是的,您可以拥有一个完整的对象图(大量数据结构),但如果它们都没有被绑定或引用,它将被垃圾收集。

如果不是,则GC未运行(您可以尝试使用GC.Collect()进行诊断,但不应在生产代码中使用它)或某些内容指的是结构的一部分。 例如,UI可能绑定到它。

循环引用对于.NET中的GC没有问题。 它使用算法来确定哪些对象实际可从某些入口点到达(例如主方法)。

然而,导致机械泄漏的是例如静态构件意外引用的物体。

您的示例属于第一类,因此可以安全使用。

在.NET中可能存在一种内存泄漏。

如果您有一个对象“A”注册到另一个对象“B”上的事件,那么“B”将获得对“A”的引用,并且如果您在“A”退出时未取消注册该事件将继续这样做范围。 在这种情况下,“A”不能被垃圾收集,因为仍然存在活动引用。 它会一直存在,直到“B”被垃圾收集。

如果你有一个“A”对象被创建并且不断超出范围的情况,你将在内存中获得越来越多的“A”。

我建议阅读.net世界中如何处理垃圾收集 – 实际上,它通过跟踪引用来查找可以被顶级对象引用的任何内容并释放其他所有东西; 它不适用于像C ++这样的析构函数,因此如果托管对象将释放父对象和祖父对象,那么管理对象将“只是去”这些知识,你会感到高兴。

当然,垃圾收集器只知道托管内存,如果你有任何非托管资源,值得查看IDisposable模式 – 这允许确定性释放非托管对象。

在处理可以引用对象的内容时会出现一个复杂的位,它确实包含一些不那么明显的东西,比如事件处理程序,这就是你提到的WPF / INotifyPropertyChanged问题来自哪里。

内存泄漏基本上是一段内存,不再需要程序的正常行为,但由于编程错误而无法释放。 因此,内存泄漏的概念与垃圾收集,C#或Java无关。

举个例子:

 var list = new List(); Node a1 = new Node(); Node a2 = new Node(); // ... Node an = new Node(); // Populate list list.Add(a1); list.Add(a2); // ... list.Add(an); // use this list DoStuffTo(list); // clear list -- release all elements list.Clear(); // memory leaks from now on 

请注意列表中的元素如何是内存泄漏,因为它们由变量a1 ... an引用

这只是一个简单的例子,说明为什么它不仅仅取决于C#来处理内存泄漏。 开发人员还有责任解决此​​问题:

 // Clear references a1 = null; a2 = null; // ... an = null; 

这将告诉C#垃圾收集器应该收集所有这些元素。

是的,一旦不再需要这些对象,就无法正确删除对象的引用,从而导致C#中的泄漏。 如果删除了对象的引用,则垃圾收集器在运行时将其除去(它会根据经过仔细调整的算法自动执行此操作,因此最好不要手动使其运行,除非你真的知道你在做什么!)。 但是如果没有正确删除对象的引用,垃圾收集器仍然认为应用程序需要它,因此内存泄漏。 特别常见的是,事件处理程序发现这种情况并没有得到妥善解决。 如果带有子/孙的对象删除了对它的所有引用,那么下次运行垃圾收集器时也会删除该对象以及所有这些子/孙子(除非它们也被从其他地方引用)。

最好的方法是使用内存分析器,它可以让你查看哪些对象在内存中保存其他对象(大多数让你拍摄内存的快照,然后查看显示引用的某种图形。如果一个对象仍然存在,那么它不应该,你可以查看一个图表,显示在内存中保存该对象的引用,并使用它来计算应该清除引用的位置,以避免内存泄漏。有一些分析器可用,但我发现ant内存红门的分析器最容易使用http://www.red-gate.com/products/dotnet-development/ants-memory-profiler/ 。