Windows窗体应用程序中的内存泄漏

我们正在开发一个大的.NET Windows Forms应用程序。 尽管我们正在处理表单,但我们正面临内存泄漏/使用问题。

场景如下:

  1. 我们的应用程序使用60 KB的内存,并在网格中显示记录列表。
  2. 当用户点击记录时,它会打开一个表单myform.showDialog ,显示详细信息。 内存从60 KB 跳到 105 MB
  3. 现在我们关闭表单myform以返回到网格,并处理该表单并将其设置为null 。 内存保持105 MB
  4. 现在,如果我们再次执行第2步,它将从105 MB跳到150 MB ,依此类推。

当我们关闭myForm时,我们怎样才能释放内存?

我们已经尝试过GC.Collect()等,但没有任何结果。

查找泄漏的第一个地方是事件处理而不是丢失Dispose()调用。 假设您的容器(父窗体)加载子窗体并为该子窗体的事件( ChildForm.CloseMe )添加处理程序。

如果要从内存中清除子表单,则必须先删除此事件处理程序,然后才能将其作为垃圾回收的候选者。

处理表格并不一定能保证您不会泄露记忆。 例如,如果您将其绑定到数据集但是在完成后没有丢弃数据集,则可能会发生泄漏。 您可能需要使用性能分析工具来识别未释放的可用资源。

顺便说一句,调用GC.Collect()是个坏主意。 只是说。

Windows窗体应用程序中最常见的内存泄漏源是事件处理程序,在表单处理后仍然保持连接…所以这是开始调查的好地方。 像http://memprofiler.com/这样的工具可以帮助确定从未进行过GC的实例的根。

至于你对GC.Collect的调用

  1. 在实践中这样做绝对不好
  2. 为了确保您的GC收集确实尽可能地收集,您需要进行多次传递并等待挂起的终结器,因此该调用是真正同步的。

     GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); GC.WaitForPendingFinalizers(); 

此外,保留表单实例的任何内容都会将表单保留在内存中,即使在表单已关闭和处理后也是如此。

喜欢:

 static void Main() { var form = new MyForm(); form.Show(); form.Close(); // The GC calls below will do NOTHING, because you still have a reference to the form! GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); GC.WaitForPendingFinalizers(); // Another thing to not: calling ShowDialog will NOT // get Dispose called on your form when you close it. var form2 = new MyForm(); DialogResult r = form2.ShowDialog(); // You MUST manually call dispose after calling ShowDialog! Otherwise Dispose // will never get called. form2.Dispose(); // As for grids, this will ALSO result in never releasing the form in // memory, because the GridControl has a reference to the Form itself // (look at the auto-generated designer code). var form3 = new MyForm(); form3.ShowDialog(); var grid = form3.MyGrid; // Note that if you're planning on actually using your datagrid // after calling dispose on the form, you're going to have // problems, since calling Dipose() on the form will also call // dispose on all the child controls. form3.Dispose(); form3 = null; } 

我最近有一个类似的问题,即使表单已关闭,正在运行的计时器将表单保存在内存中。 解决方案是在关闭表单之前停止计时器。

我没有看到你的代码,但这是最可能的情况:

1)您的表单已关闭,但有一个引用它挂在那里,不能被垃圾收集。

2)您正在加载一些无法释放的资源

3)您正在使用XSLT并在每次转换时编译它

4)您有一些在运行时编译和加载的自定义代码

某些第三方控件的代码中存在错误。 如果您正在使用其中一些控件,则可能不是您的问题。

检查您是否已完全删除对表单的所有引用。 有时可能会发生一些您没有注意到的隐藏引用。

例如:如果您附加到对话框中的外部事件,即外部窗口的事件,如果您忘记取消它们,您将有一个永远不会消失的表单的剩余引用。

在您的对话框中尝试此代码(示例错误代码…):

  protected override void OnLoad(EventArgs e) { Application.OpenForms[0].Activated += new EventHandler(Form2_Activated); base.OnLoad(e); } void Form2_Activated(object sender, EventArgs e) { Console.WriteLine("Activated!"); } 

并且多次打开和关闭对话框,您将在控制台中为每个调用增加字符串数量。 这意味着即使您调用dispose(即应该只用于释放非托管资源,即:关闭文件和类似的东西),表单仍保留在内存中。

首先,检查您的表单的引用。 您的表单是否订阅了任何活动? 这些作为参考,如果事件发布者的寿命超过您的表单,那么它将保留您的表单(除非您取消订阅)。

它也可能是巧合 – 我相信.NET会分段分配内存,所以你可能看不到你的工作集随着每个表单发布而下降(内存是由你的表单发布的,但是仍然保留给下一个分配你的申请)。 由于你的内存分配至少是一个抽象,你不会总是得到你的工作集上下你所分配的确切字节数的行为。

测试这种方法的一种方法是创建表单的大量实例并释放它们 – 尝试放大泄漏,以便分配和释放100个实例。 你的记忆是否继续boost而不会下降(如果是这样,你有问题),还是最终恢复到接近正常状态? (可能不是问题)。