循环引用导致内存泄漏?

我试图在Windows窗体应用程序中运行内存泄漏。 我现在正在看一个包含几个embedded表单的表单。 令我担心的是,孩子在构造函数中形成父表单,并将其保存在私有成员字段中。 所以在我看来垃圾收集时间:

家长通过控件集合(子表单embedded在那里)引用了子表单。 子表格不是GC'd。

子表单通过私有成员字段具有对父表单的引用。 父表格不是GC'd。

这是一个准确的了解如何垃圾收集器将评估的情况? 任何方式来“certificate”它的testing目的?

好问题!

不,这两种forms都将(可以)GC'd,因为GC不直接查找其他参考中的引用。 它只查找所谓的“Root”引用…这包括堆栈上的引用variables(variables位于堆栈上,实际对象当然位于堆上),引用CPU寄存器中的variables以及引用variables类中的静态字段…

所有其他的引用variables,如果它们在由上述过程find的“根”引用对象之一的属性中被引用(或者GC'd)…(或者在根对象中的引用所引用的对象中等等…)

所以只有当其中一个表单被引用到其他地方的“root”引用中时 – 那么这两个表单对于GC来说都是安全的。

只有这样我才能想到“certificate”它(不使用内存跟踪实用程序)将创build几十万个这样的forms,在一个方法内部的循环中,然后在方法中查看应用程序的内存占用情况,然后退出该方法,调用GC,再次查看脚印。

正如其他人已经说过的,GC在循环引用中没有问题。 我只想补充一点,在.NET中泄漏内存的常见场所是事件处理程序。 如果你的一个表单有一个附加的事件处理程序到另一个“活着”的对象,那么就有一个对你的表单的引用,表单将不会被GC_d。

垃圾收集工作通过跟踪应用程序根。 应用程序根目录是包含托pipe堆上的对象的引用的存储位置(或为空)。 在.NET中,根源是

  1. 引用全局对象
  2. 引用静态对象
  3. 引用静态字段
  4. 堆栈上的引用到本地对象
  5. 在堆栈上引用传递给方法的对象参数
  6. 对等待完成的对象的引用
  7. 在CPU寄存器中引用托pipe堆上的对象

活动根的列表由CLR维护。 垃圾收集器的工作原理是查看托pipe堆上的对象,并查看应用程序仍可访问哪些对象,即可通过应用程序根访问。 这样的对象被认为是根深蒂固的。

现在假设你有一个父窗体包含对子窗体的引用,并且这些子窗体包含对父窗体的引用。 此外,假设应用程序不再包含对父表项或任何子表单的引用。 然后,出于垃圾收集器的目的,这些被pipe理的对象不再被固定,并在下一次发生垃圾收集时被垃圾收集。

如果家长和孩子都没有被引用,但他们只引用彼此,他们确实得到GCed。

获取一个内存分析器来真正检查你的应用程序,并回答你所有的问题。 我可以推荐http://memprofiler.com/

我想回应Vilx关于事件的评论,并推荐一个有助于解决的devise模式。

假设您有一个types是事件源,例如:

interface IEventSource { event EventHandler SomethingHappened; } 

以下是处理来自该types实例的事件的类的片段。 这个想法是,只要你分配一个新的实例属性,你首先退订任何以前的任务,然后订阅新的实例。 空检查确保正确的边界行为,更重要的是,简化处置:你所做的只是空的属性。

这带来了处置的重点。 任何订阅事件的类都应该实现IDisposable接口,因为事件是被pipe理的资源。 (为了简洁起见,我在示例中跳过了Dispose模式的适当实现,但您明白了。)

 class MyClass : IDisposable { IEventSource m_EventSource; public IEventSource EventSource { get { return m_EventSource; } set { if( null != m_EventSource ) { m_EventSource -= HandleSomethingHappened; } m_EventSource = value; if( null != m_EventSource ) { m_EventSource += HandleSomethingHappened; } } } public Dispose() { EventSource = null; } // ... } 

GC可以正确处理循环引用,如果这些引用是保持表单活着的唯一东西,那么它们将被收集。
我有很多麻烦.net不从forms回收内存。 在1.1版本中,有一些bug是由menuitem(我认为)造成的,这意味着它们没有被处置,并且可能会泄漏内存。 在这种情况下,添加显式调用来处理和清除表单的Dispose方法中的成员variables对问题进行sorting。 我们发现这也有助于回收一些其他控制types的内存。
我也花了很长时间与CLR分析器一起查看为什么表单没有被收集。 据我所知,这个框架引用了这个框架。 每个表单types一个。 所以如果你创build了Form1的100个实例,那么closures它们,只有99个会被正确的回收。 我没有find任何方法来解决这个问题。
我们的应用程序已经转移到.net 2,这似乎是好多了。 我们的应用程序内存仍然增加,当我们打开第一个窗体时,不closures,但是我相信这是因为JIT的代码和额外的控制库加载。
我也发现,虽然GC可以处理循环引用,但它似乎有问题(有时)与循环事件处理程序引用。 IE对象1引用对象2和对象1有一个方法来处理和来自对象2的事件。 我发现当我预期的时候,这并没有释放对象,但是我从来没有能够在testing用例中重新生成它。