zoukankan      html  css  js  c++  java
  • 未释放事件Handler可能导致内存泄漏

    以前曾看见过这样一个问题:托管代码会不会导致内存泄漏。自己对GC的了解也不是很深,但还是比较赞成这样的观点:托管代码不会产生内存泄漏,除非你没有正确释放非托管资源。
    今天看到一个非常有趣的例子,关于没有释放事件的Handler导致的内存泄漏。
    以前对于释放Handler的观念是一点也没有,这主要因为没此方面的意识,没有养成好的习惯。只知道当关心这个事件的时候就注册一下, 暂时不关心了就移除掉。却从来没有想到最终不移除不必要的Handler会导致此类无法被正常回收,导致不必要的内存浪费。

    事情是这样的,今天在看项目Source Code的时候发现一个有趣的字眼:"WeakEvent". 自己以前对WeakReference有点了解,所以就好奇地看看这是个啥玩意。
    发现其是一种通过弱引用实现的Delegate。因为没有太多的注释,所有不知其为啥用此种方式来封装事件。于是顺手Google了一下,找到了一篇关于weak event的非常有意思的文章。
    文章里提出了一个问题,场景如下:


    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.Win32;

    namespace ConsoleApplication16
    {
        
    class DisplaySettingsListener
        
    {
            
    byte[] m_ExtraMemory = new byte[1000000];

            
    public DisplaySettingsListener()
            
    {
                SystemEvents.DisplaySettingsChanged 
    += new EventHandler(ehDisplaySettingsChanged);
            }


            
    private void ehDisplaySettingsChanged(object sender, EventArgs e)
            
    {
            }

        }


        
    class Program
        
    {
            
    static void DisplayMemory()
            
    {
                Console.WriteLine(
    "Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));
            }


            
    static void Main()
            
    {
                DisplayMemory();
                Console.WriteLine();
                
    for (int i = 0; i < 5; i++)
                
    {
                    Console.WriteLine(
    "--- New Listener #{0} ---", i + 1);
                    DisplaySettingsListener listener 
    = new DisplaySettingsListener();
                    listener 
    = null;
                    GC.Collect();

                    DisplayMemory();
                }

                Console.Read();
            }


        }

    }

    运行的结果如下:

     image

    虽然我们释放了对listener的引用,并且强制GC进行回收,但我们可以看到其内存占用量还是变大了,出乎了我的意料。
    这就是该文作者指出的事件列表里保存的是一个强引用而非弱引用。虽然上面释放了listener变量对Listener实例的引用,但因为仍然在DisplaySettingsChanged事件列表里保存了对Listener实例的引用,导致Listener实例并不能被垃圾回收(有人引用,自然不会回收)。
    那么接下来看看下面的代码:

    Release Event Hanlder

    运行结果如下:

    image 
    结果是不是正如您猜测的呢:)。已经成功地回收了listener实例。 不知为何从432944字节变到446980字节,哪位高手赐教一下啊:)
    详情可以看原文    The Problem With Delegates
    在后续的文章中作者类似文章开头提到的Weak Event来解决这个问题: Solving the Problem with Events: Weak Event Handlers

    也许您觉得写这样的一个Weak Event没有必要或者显得麻烦,但您一定要记得及时地在必要的地方调用 -= 取消不再关心的事件。本文的目的也只是在此方面提个善意的提醒。

    1.在CLR中值类型/引用类型中static变量被AppDomain中的Handle Table所mark, 并且为pinned类型, 因此GC并不负责回收之直到
    AppDomain被unloaded.
    2.Inside delegate我们可以发现, Delegate往往保存了对target(this指针)的reference
    3.结合1,2, 由于static变量一直没被GC, 同时它又不负责任的hold了对this的引用, 最终导致了this所指的对象一直在memory中

  • 相关阅读:
    windows下的SASS/Compass的安装与卸载
    玩转HTML5移动页面(优化篇)
    小谈数组去重
    前端问题解答
    JavaScript使用封装
    JavaScript使用接口
    JavaScript精要(系列)
    JavaScript DOM节点和文档类型
    JavaScript数组类型
    JavaScript函数表达式
  • 原文地址:https://www.cnblogs.com/qianyz/p/1844920.html
Copyright © 2011-2022 走看看