垃圾回收和GCHandle.Alloc

本文关键字:GCHandle Alloc | 更新日期: 2023-09-27 17:56:50

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 时,计时器类正在请求 GC 不使用 - GCHandle.Alloc 进行收集。伙计们,这是内存泄漏的一大来源。除非我设置 t.Enabled = false,否则即使我们关闭表单,整个表单也会泄漏。

在第一个示例代码中,我无法理解为什么即使在我触发 GC 后也没有对表单进行垃圾回收。收集()。在反射器中,我看到ControlNativeWindow已经在内部使用GCHandle.Alloc的Form中使用。这是一个原因吗?作为 .NET 库的用户,我一直认为,当引用无法访问时,它将有机会进行垃圾回收。当然,垃圾回收和从内存中实际释放是不确定的。但我的问题是 - 我对这两个例子的理解是否正确?当有些对象即使在无法访问后也可以存活时,我将如何跟踪它以防止内存泄漏?

垃圾回收和GCHandle.Alloc

Winforms保留了一个内部表,该表将句柄映射到控件实例。 该表可确保只要本机窗口处于活动状态,就永远不会对控件(在您的情况下为窗体)进行垃圾回收。 当窗口被销毁时,它被用户关闭窗体或您的代码释放它,从该表中删除。

System.Timers.Timer 由 CLR 引用的 cookie 保持活动状态。 该类是使用 System.Threading.Timer 实现的,该 System.Threading.Timer 具有一个采用状态对象参数的构造函数。 该状态对象是cookie,CLR使用等效的GCHandle.Alloc()对其进行引用。 禁用计时器将重置允许对计时器进行垃圾回收的 cookie。

这些

是框架防止这些对象过早收集垃圾的自然且必要的方法。 您只能通过在处置表单时忘记禁用计时器来导致泄漏。 这通常是非常不健康的,您不希望计时器在表单死时继续滴答作响。 将 Dispose 方法从设计器.cs文件移动到窗体代码中,或重写 OnFormClosed() 以禁用计时器。

您必须

自己管理 FormSystem.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);
}

因此,实际上,只要您关闭表单而不仅仅是隐藏它们,就不会发生内存泄漏。但是,如果您手动创建了计时器,则必须在关闭表单时自行处理它。

另一方面,如果您没有任何引用,即使您没有Dispose它,System.Threading.Timer它也将被垃圾收集。