以前曾看见过这样一个问题托管代码会不会导致内存洩漏自己对GC的了解也不是很深但还是比较赞成这样的观点托管代码不会产生内存洩漏除非你没有正确释放非托管资源 今天看到一个非常有趣的例子关于没有释放事件的Handler导致的内存洩漏 以前对于释放Handler的观念是一点也没有这主要因为没此方面的意识没有养成好的习惯只知道当关心这个事件的时候就注册一下 暂时不关心了就移除掉却从来没有想到最终不移除不必要的Handler会导致此类无法被正常回收导致不必要的内存浪费 事情是这样的今天在看项目Source Code的时候发现一个有趣的字眼WeakEvent 自己以前对WeakReference有点了解所以就好奇地看看这是个啥玩意 发现其是一种通过弱引用实现的Delegate因为没有太多的注释所有不知其为啥用此种方式来封装事件于是顺手Google了一下找到了一篇关于weak event的非常有意思的文章 文章里提出了一个问题场景如下 UnRelease Event Handler using System using SystemCollectionsGeneric using SystemText using MicrosoftWin namespace ConsoleApplication { class DisplaySettingsListener { byte[] m_ExtraMemory = new byte[] public DisplaySettingsListener() { SystemEventsDisplaySettingsChanged += new EventHandler(ehDisplaySettingsChanged) } private void ehDisplaySettingsChanged(object sender EventArgs e) { } } class Program { static void DisplayMemory() { ConsoleWriteLine(Total memory {###########} bytes GCGetTotalMemory(true)) } static void Main() { DisplayMemory() ConsoleWriteLine() for (int i = i < i++) { ConsoleWriteLine(—— New Listener #{} —— i + ) DisplaySettingsListener listener = new DisplaySettingsListener() listener = null GCCollect() DisplayMemory() } ConsoleRead() } } } 运行的结果如下 虽然我们释放了对listener的引用并且强制GC进行回收但我们可以看到其内存占用量还是变大了出乎了我的意料 这就是该文作者指出的事件列表里保存的是一个强命名的引用而非弱引用虽然上面释放了listener变量对Listener实例的引用但因为仍然在DisplaySettingsChanged事件列表里保存了对Listener实例的引用导致Listener实例并不能被垃圾回收(有人引用自然不会回收) 那么接下来看看下面的代码 Release Event Hanlder class DisplaySettingsListener IDisposable { byte[] m_ExtraMemory = new byte[] public DisplaySettingsListener() { SystemEventsDisplaySettingsChanged += new EventHandler(ehDisplaySettingsChanged) } private void ehDisplaySettingsChanged(object sender EventArgs e) { } IDisposable Members#region IDisposable Members public void Dispose() { SystemEventsDisplaySettingsChanged = new EventHandler(ehDisplaySettingsChanged) } #endregion } class Program { static void DisplayMemory() { ConsoleWriteLine(Total memory {###########} bytes GCGetTotalMemory(true)) } static void Main() { DisplayMemory() ConsoleWriteLine() for (int i = i < i++) { ConsoleWriteLine(—— New Listener #{} —— i + ) DisplaySettingsListener listener = new DisplaySettingsListener() listenerDispose() listener = null GCCollect() DisplayMemory() } ConsoleRead() } } 运行结果如下 结果是不是正如您猜测的呢)已经成功地回收了listener实例 不知为何从字节变到字节哪位高手赐教一下啊) 详情可以看原文 The Problem With Delegates 在后续的文章中作者类似文章开头提到的Weak Event来解决这个问题 Solving the Problem with Events Weak Event Handlers 也许您觉得写这样的一个Weak Event没有必要或者显得麻烦但您一定要记得及时地在必要的地方调用 = 取消不再关心的事件本文的目的也只是在此方面提个善意的提醒 |