情况如下:
1.我们的应用程序在网格中显示一组记录,占用60 KB的内存。
2.当用户单击记录时,它会打开一个窗体myform.showDialog,显示详细信息。内存从60 KB跳到105 MB。
3.现在我们关闭窗体myform以返回网格,并且dispose该窗体并将其设置为null。内存仍保持在105 MB。
4.现在,如果我们再次执行步骤2,它将从105 MB跳到150 MB等。
我们如何在关闭myform时释放内存?
我们已经尝试过GC.Collect()等方法,但没有任何结果。
寻找泄漏问题的第一步是检查事件处理程序,而不是缺少Dispose()
调用。假设您的容器(即父窗体)加载了一个子窗体并添加了该子窗体的事件处理程序(ChildForm.CloseMe
)。
如果子窗体将被清除以释放内存,则必须在它成为垃圾收集器的候选项之前移除此事件处理程序。
void Dispose(bool disposing)
模式(http://msdn.microsoft.com/en-us/library/fs2xkftw.aspx),以及事件处理程序的`+=`和`-=`。 - Travis GockelDispose(bool)
。我的第一个Stack Overflow问题之一也是关于它的:http://stackoverflow.com/questions/773165/why-does-vs2005-vb-net-implement-the-idisposable-interface-with-a-disposedisposi - STWRemoveHandler / -=
调用,以帮助保险起见--特别是如果 GC 不是很理解。 - STWGC.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;
}
perfmon
的进程指标来测量私有字节等内容。任务管理器曾经表明通过最小化和恢复应用程序可以达到约95%的内存减少,这正说明了其报告是多么的不准确。 - STW释放表单并不一定保证您不会泄漏内存。例如,如果您将其绑定到数据集但在完成后没有释放数据集,则可能会出现泄漏。您可能需要使用分析工具来识别未被释放的可清除资源。
顺带一提,调用GC.Collect()是一个坏主意。只是说一下。
GC.Collect()
的话,我就会使用“总是”而不是“频繁”。 - Andrew Barber首先,检查您的表单是否有任何事件订阅。这些都算作引用,如果事件发布者的生命周期比您的表单长,那么它将保留您的表单(除非您取消订阅)。
这也可能是巧合--我相信.NET在段中分配内存,因此您可能不会看到每个表单释放时工作集下降的情况(内存由表单释放,但仍为应用程序的下一个分配保留)。由于您的内存分配至少有一层抽象,因此您不会总是获得您的工作集上下移动与您分配的确切字节数相同的行为。
测试的方法是创建大量的表单实例并释放它们--尝试放大泄漏,以便您分配和释放数百个实例。您的内存是否继续上升而不下降(如果是,则存在问题),还是最终返回接近正常?(可能没有问题)。
最近我遇到了一个类似的问题,即一个正在运行的计时器使得表单在关闭后仍然保留在内存中。解决方法是在关闭表单之前停止计时器。
请确保您已经完全删除了所有对该表单的引用。有时候可能会出现一些您没有注意到的隐藏引用。
例如:如果您从对话框中连接到外部事件,即外部窗口的事件,如果您忘记从它们中断开连接,那么您将会有一个剩余的对该表单的引用,这个引用永远不会消失。
在您的对话框中尝试以下代码(示例糟糕的代码...):
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!");
}
我没有看到你的代码,但这是最有可能的情况:
1)你的表单关闭了,但还有一个引用挂着,不能被垃圾回收。
2)你加载了一些资源,但没有释放它们
3)你正在使用XSLT,并在每次转换时编译它
4)你有一些定制的代码是在运行时编译和加载的
GC.Collect()
也不能保证内存将被释放。幸存的对象可能会晋升到更高的代,这意味着它们将不经常被检查以进行回收。 - Andrew BarberXmlSerializer
的某些构造函数而言,它们每次被调用时还会动态生成并加载一个新的程序集--如果不手动缓存和检索它们的结果,那么它们很快就会成为一个泄漏。 - STW一些第三方控件在其代码中存在错误。如果您正在使用其中一些控件,则可能不是您的问题。