请问由于事件处理程序而导致的 .NET 内存泄漏示例



这里和那里人们一直在谈论由于未发布的事件侦听器而发生的内存泄漏。我认为这是一个非常重要的问题。非常严重,非常重要...如果它真的存在。

尝试自己重现该问题,但所有尝试都失败了:我只是无法使我的应用程序泄漏内存:(虽然听起来不错,但我仍然担心:也许我错过了什么。

所以也许有人可以提供一个简单的源代码示例,导致内存泄漏?

我创建了一个小型 VB.NET 应用程序作为演示:它包含一个Windows窗体和一个类。

Windows表单:它有一个集合对象(名为"c")和两个按钮:一个用于向集合添加10个项目,另一个用于清除集合:

Public Class Form1
Dim c As New Collection
Private Sub btnAddItem_Click(sender As System.Object, e As System.EventArgs) Handles btnAddItem.Click
    For i As Integer = 1 To 10
        Dim m As New MyType
        c.Add(m)
    Next
    Me.Text = c.Count
End Sub
Private Sub btnClear_Click(sender As System.Object, e As System.EventArgs) Handles btnClear.Click
    For Each item As MyType In c
        item.Dispose()
    Next
    c.Clear()
    Me.Text = c.Count
End Sub
End Class

MyType 类:它有大m_Image对象,它很大,所以你可以看到你的内存真的被 MyType 实例占用了:)

Imports System.Drawing
Public Class MyType 
Implements IDisposable
Private m_Image As Bitmap
Public Sub New()
    AddHandler Application.Idle, AddressOf Application_Idle
    m_Image = New Bitmap(1024, 1024)
End Sub
Private Sub Application_Idle(sender As Object, e As EventArgs)
End Sub
#Region "IDisposable Support"
Private disposedValue As Boolean
Protected Overridable Sub Dispose(disposing As Boolean)
    If Not Me.disposedValue Then
        If disposing Then
            m_Image.Dispose()
        End If
    End If
    Me.disposedValue = True
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
    Dispose(True)
    GC.SuppressFinalize(Me)
End Sub
#End Region
End Class

这是一个非常简单的例子

class MyType
{
    public static event EventHandler ExampleEvent;
    public MyType()
    {
        ExampleEvent += (sender, e) => OnExampleEvent();
    }
    private void OnExampleEvent() { }
}

MyType的任何实例都将订阅ExampleEvent事件。 此事件不附加到任何特定对象,因此它永远不会离开内存。 这将在应用程序期间将所有MyType实例保存在内存中。

编辑

正如评论中要求的那样,这里是MyType实例在不再使用后仍保留在内存中的演示

class Program
{
    static void Main(string[] args)
    {
        WeakReference weakRef = new WeakReference(new MyType());
        for (var i = 0; i < 10; i++)
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
        Console.WriteLine("Still Alive: {0}", weakRef.IsAlive);
    }
}

经过更多的调查(感谢@JaredPar的线索),我发现当我们遇到这种情况时会发生内存泄漏:

  1. 创建对具有公共过程或函数(例如过程 PRC1())。
  2. 从代码中的任何位置添加事件处理程序:将任何事件(例如 EVNT1)链接到 REF1 对象中的 PRC1 过程。
  3. 删除 REF1(在 VB 中将其设置为 null 或 Nothing)。从这一刻起,您将没有对在步骤 1 中创建的对象的引用。
  4. 然而,对象保留在内存中,因为它是合乎逻辑的:它拥有一个代码(PRC1),该代码在事件触发时执行(EVNT1)。

虽然我没有给你任何建议,如何在这种情况下释放内存,但我希望这个描述能帮助你设计更好的架构并避免内存泄漏。

当一个对象要求另一个对象通知某些事情已经发生时,就会发生事件成为内存(和 CPU 时间!)泄漏的最常见模式,以便它可以更新一些仅对短期对象感兴趣的信息。 如果事件订阅继续存在,即使过去关心它的所有对象都已放弃,只要执行通知的对象继续存在,内存就会浪费,并且每次对象执行通知时都会浪费 CPU 时间。 如果可以创建和放弃无限数量的此类事件订阅,则它们将构成无限内存泄漏。

相关内容

  • 没有找到相关文章

最新更新