void Foo()
{
System.Windows.Forms.Form f = new System.Windows.Forms.Form();
f.Show();
}
据我所知,f持有对表格的引用。但是f是一个局部变量,当控件离开花括号时它将超出范围。但表格仍然开放。我尝试调用GC.Collect(),但表单仍处于打开状态。
另一种情况。
private void button2_Click(object sender, EventArgs e)
{
Timer t = new Timer();
t.Enabled = true;
t.Interval = 1000;
t.Tick += new EventHandler(t_Tick);
}
void t_Tick(object sender, EventArgs e)
{
}
在这种情况下,t永远不会被垃圾收集。经过大量的研究,我发现当我设置t.Enabled = true时,Timer类正在请求GC不使用-GCHandle.Alloc。大家好,这是内存泄漏的一个重要来源。除非我设置t.Enabled = false,否则即使我们关闭表格,整个表格也会被泄露。
在第一个示例代码中,我无法理解为什么即使在我触发GC.Collect()之后Form也没有收集垃圾。在反射器中,我看到ControlNativeWindow已经在Form中使用,它在内部使用GCHandle.Alloc。这是一个原因吗?作为.NET库的用户,我始终认为当引用无法访问时,它将有机会进行垃圾回收。当然垃圾收集和内存中的实际释放是不确定的。但我的问题是 - 我的理解是否适用于这两个例子?当有对象即使在无法访问后仍然可以存活时,那么我将如何跟踪它以防止内存泄漏?
答案 0 :(得分:4)
Winforms保留一个内部表,将句柄映射到控件实例。该表确保只要本机窗口处于活动状态,就不会对控件(在您的情况下为表单)进行垃圾回收。当窗口被销毁时,它会被关闭表单或处理它的代码从表中删除。
System.Timers.Timer由CLR引用的cookie保持活动状态。该类使用System.Threading.Timer实现,该系统具有一个带有 state 对象参数的构造函数。 state 对象是cookie,CLR使用等效的GCHandle.Alloc()来引用它。禁用计时器会重置cookie,从而允许对计时器进行垃圾回收。
这些是框架防止这些对象过早收集垃圾的自然而必要的方法。您只能在处理表单时忘记禁用计时器而导致泄漏。这通常是非常不健康的,你不希望计时器在表单死亡时继续滴答作响。将Dispose方法从Designer.cs文件移动到表单代码中或覆盖OnFormClosed()以禁用计时器。
答案 1 :(得分:1)
您必须自己管理Form
和System.Windows.Forms.Timer
个实例的生命周期。您需要致电Dispose
以标记其生命周期结束,GC
将在之后收集它们。关闭表单会在内部调用Dispose
。如果使用设计器将计时器放在表单上,则表单将在表单关闭时处理。这是通过设计器在表单构造函数中生成以下代码来实现的:
this.components = new System.ComponentModel.Container();
this.timer1 = new System.Windows.Forms.Timer(this.components);
表单的和Dispose
为:
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
所以实际上,只要你关闭表单而不是隐藏它们就没有内存泄漏。但是如果您手动创建了计时器,则必须在表单关闭时自行处理它。
另一方面,System.Threading.Timer
如果您没有Dispose
,则会对其进行垃圾收集。