【问题标题】:Example of memory leak in .NET due to event handlers please?.NET 中由于事件处理程序导致的内存泄漏示例?
【发布时间】:2012-04-20 19:46:31
【问题描述】:

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

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

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

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

【问题讨论】:

  • 这在某种程度上取决于您对内存泄漏的定义,以及在什么情况下。我们谈论的是网络应用还是桌面应用?
  • 好吧,如果它有所作为,很高兴看到两种应用类型的代码示例:桌面和网络。

标签: .net events memory memory-leaks event-handling


【解决方案1】:

这是一个非常直接的例子

class MyType
{
    public static event EventHandler ExampleEvent;

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

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

编辑

根据 cmets 的要求,这里是 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);
    }
}

【讨论】:

  • 我不确定这是内存泄漏的一个例子,因为您专门订阅了静态全局事件。这有点像说您有内存泄漏,因为 main 在应用程序退出之前不会退出。
  • @MystereMan OP 要求基于事件的内存泄漏,这是一个示例。对于生命周期大于订阅该事件的对象的预期生命周期的任何事件,都可以重复此操作。
  • 感谢 JaredPar 的快速回复。但是,我在您的示例中看不到问题。我编辑了我的主题:添加了示例代码(它在 VB 中)。在我的示例中,MyType 订阅了 Application.Idle 事件,该事件在整个应用程序生命周期内可用(无法找到 SystemEvents.TimeChanged 事件;))。
  • @Dima 如果您的对象打算与应用程序一样长,那么它不是内存泄漏。内存泄漏通常由对象在内存中保存的时间超过其预期用途来定义。在这种特殊情况下,它看起来像是打算活得那么久,因此不是问题。
  • 好吧,假设我的演示应用程序(来自主题的演示)的主要任务是用虚拟对象 (MyType) 填充集合,并清除集合。所以我可以按“添加”按钮 10 次并在集合中获取一包对象。例如,它将占用 100MB。然后我可以清除集合。 100MB 被释放。我可以再次创建一组对象。填充 200MB。再次清除集合 - 释放 200MB 内存。所以我无法理解的是:内存泄漏在哪里?
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 您好,一般的内存泄漏不会因简单的事件使用而发生。特定的不良设计会导致内存泄漏。不是事件本身。您可以在以前的答案中看到对不良设计情况的描述。
    猜你喜欢
    • 1970-01-01
    • 2015-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-13
    • 2012-11-02
    • 2016-02-15
    • 1970-01-01
    相关资源
    最近更新 更多