6

人们不断地谈论由于未释放的事件侦听器而发生的内存泄漏。我认为这是一个非常重要的问题。非常严肃,非常重要……如果它真的存在的话。

我已经尝试自己重现这个问题,但我所有的尝试都失败了:我就是不能让我的应用程序泄漏内存:(虽然听起来不错,但我仍然担心:也许我错过了一些东西。

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

我创建了一个小型 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
4

3 回答 3

12

这是一个非常直接的例子

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);
    }
}
于 2012-04-07T16:34:33.590 回答
0

After more investigation (thanks to clues from @JaredPar), I found out that memory leak occurs when we have such conditions:

  1. Create reference REF1 to new object which has public procedure or function (for example procedure PRC1()).
  2. From any place in your code add event handler: link any event (for example EVNT1) to PRC1 procedure in REF1 object.
  3. Remove REF1 (set it to null or Nothing in VB). From this moment you have no references to the object you have created in step 1.
  4. However object stays in memory, since it is logical: it possesses a code (PRC1) which is executed when event fires (EVNT1).

While I don't give you any advice how to free your memory in such situation, I hope this description will help you design better architecture and avoid memory leaks.

于 2012-04-08T09:08:45.777 回答
0

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

于 2012-05-04T22:17:11.830 回答