.net中事件引起的内存泄漏分析

系列主题:基于消息的软件架构模型演变

在Winform和Asp.net时代,事件被大量的应用在UI和后台交互的代码中。看下面的代码:

        private void BindEvent()
        {
            var btn = new Button();
            btn.Click += btn_Click;
        }

        void btn_Click(object sender, EventArgs e)
        {
            MessageBox.Show("click");
        }

这样的用法可以引起内存泄漏吗?为什么我们平时一直写这样的代码从来没关注过内存泄漏?等分析完原因后再来回答这个问题。

为了测试原因,我们先写一个EventPublisher类用来发布事件:

    public class EventPublisher
    {
        public static int Count;

        public event EventHandler<PublisherEventArgs> OnSomething;

        public EventPublisher()
        {
            Interlocked.Increment(ref Count);
        }

        public void TriggerSomething()
        {
            RaiseOnSomething(new PublisherEventArgs(Count));
        }

        protected void RaiseOnSomething(PublisherEventArgs e)
        {
            EventHandler<PublisherEventArgs> handler = OnSomething;
            if (handler != null) handler(this, e);
        }

        ~EventPublisher()
        {
            Interlocked.Decrement(ref Count);
        }
    }

这个类提供了一个事件OnSomething,另外在构造函数和析构函数中分别会对变量Count进行累加和递减。Count的数量反应了EventPublisher的实例在内存中的数量。

写一个Subscriber用来订阅这个事件:

    public class Subscriber
    {
        public string Text { get; set; }
        public List<StringBuilder> List = new List<StringBuilder>();
        public static int Count;
        public Subscriber()
        {
            Interlocked.Increment(ref Count);
            for (int i = 0; i < 1000; i++)
            {
                List.Add(new StringBuilder(1024));
            }
        }

        public void ShowMessage(object sender, PublisherEventArgs e)
        {
            Text = string.Format("There are {0} publisher in memory",e.PublisherReferenceCount);
        }

        ~Subscriber()
        {
            Interlocked.Decrement(ref Count);
        }
    }

Subscriber同样用Count来反映内存中的实例数量,另外我们在构造函数中使用StringBuilder开辟1000*1024Size的大小以方便我们观察内存使用量。

最后一步,写一个简单的winform程序,然后在一个Button的Click事件中写入测试代码:


         private void btnStartShortTimePublisherTest_Click(object sender, EventArgs e)
        {
            for (int i = 0; i < 100; i++)
            {
                var publisher = new EventPublisher();
                publisher.OnSomething += new Subscriber().ShowMessage;
                publisher.TriggerSomething();
            }

            MessageBox.Show(string.Format("There are {0} publishers in memory, {1} subscribers in memory", EventPublisher.Count, Subscriber.Count));
        }

for循环中的代码是一个很普通的事件调用代码,我们将Subscriber实例中的ShowMessage方法绑定到了publisher对象的OnSomething事件上,为了观察内存的变化我们循环100次。

执行结果如下:

publisher和subscriber的数量都为3,这并不代表发生了内存泄漏,只不过是没有完全回收完毕而已。每个publisher在出了for循环后就会被认为没有任何用处,从而被正确回收。而注册在上面的观察者subscriber也能被正确回收。

再放一个Button,并在Click中写以下测试代码:

        private void BtnStartLongTimePublisher_Click(object sender, EventArgs e)
        {
            for (int i = 0; i < 100; i++)
            {
                var publisher = new EventPublisher();
                publisher.OnSomething += new Subscriber().ShowMessage;
                publisher.TriggerSomething();
                LongLivedEventPublishers.Add(publisher);
            }
            MessageBox.Show(string.Format("There are {0} publishers in memory, {1} subscribers in memory", EventPublisher.Count,Subscriber.Count));
        }

这次for循环中不同之处在于我们将publisher保存在了一个list容器当中,从而保证100个publisher不能垃圾回收。这次的执行结果如下:

我们看到100个subscribers全部保存在内存中。如果观察资源管理器中的内存使用率,你也能发现内存突然涨了几百兆并且再不会减少。

想一下下面的场景:

public class Runner
    {
        private LongTimeService _service;

        public Runner()
        {
             _service = new LongTimeService();

        }
        public void Run()
        {
            _service.SomeThingUpdated += (o, e) => { /*do some thing*/};
            _service.SomeThingUpdated += (o, e) => { /*do some thing*/};
            _service.SomeThingUpdated += (o, e) => { /*do some thing*/};
            _service.SomeThingUpdated += (o, e) => { /*do some thing*/};
        }
    }

LongTimeService是一个长期运行的服务,从来不被销毁,这将导致所有注册在SomeThingUpdated 事件上的观察者也不会能回收。当有大量的观察者不停的注册在SomeThingUpdated 上时,内存就会发生泄漏。

这三个测试说明了引起事件内存泄漏的场景:当观察者注册在了一个生命周期长于自己的事件主题上,观察者不能被内存回收。

解决办法是在事件上显示调用-=符号。

再回过头来看开始提出来的问题:当使用了Button的Click事件的时候,会发生内存泄漏吗?

btn.Click += btn_Click;

观察者是谁?btn_Click方法的拥有者,也就是Form实例。

主题是谁?Button的实例btn

主题btn什么时候销毁?当Form实例被销毁的时候。

当Form被销毁的时候,btn及其观察者都会被销毁。除非Form从来不销毁,并且大量的观察者持续注册在了btn.Click上才能发生内存泄漏,当然这种场景是很少见的。所以我们开发winform或者asp.net的时候一般来说并不会关心内存泄漏的问题。

时间: 2024-10-24 02:35:54

.net中事件引起的内存泄漏分析的相关文章

Node.js内存泄漏分析

在极客教育出版了一个视频是关于<Node.js 内存泄漏分析>,本文章主要是从内容上介绍如何来处理Node.js内存异常问题.如果希望学习可前往极客学院: 本文章的关键词 - 内存泄漏 - 内存泄漏检测 - GC分析 - memwatch 文章概要 由于内存泄漏在Node.js中非常的常见,可能在浏览器中应用javascript时,对于其内存泄漏不是特别敏感,但作为服务器语言运行时,你就不得不去考虑这些问题.由于很小的逻辑可能导致服务器运行一天或者一个星期甚至一个月才会让你发现内存不断上涨,而

解析Java的JNI编程中的对象引用与内存泄漏问题

JNI,Java Native Interface,是 native code 的编程接口.JNI 使 Java 代码程序可以与 native code 交互--在 Java 程序中调用 native code:在 native code 中嵌入 Java 虚拟机调用 Java 的代码.JNI 编程在软件开发中运用广泛,其优势可以归结为以下几点: 利用 native code 的平台相关性,在平台相关的编程中彰显优势. 对 native code 的代码重用.native code 底层操作,更

Javascript的内存泄漏分析

作为程序员(更高大尚的称谓:研软件研发)的我们,无论是用Javascript,还是.net, java语言,肯定都遇到过内存泄漏的问题.只不过他们都有GC机制来帮助程序员完成内存回收的事情,如果你是C++开发者(你懂的).....,如果你是前端开发者,肯定在使用Javascript(你或者会说,Js是世界上最棒的语言),但我这里也得告诉你,Js的内存泄漏会来得更为突然,或者让你都无法察觉.本文就带大家领略一下Js的风骚: 一.模块化引起的内存泄漏 代码如下: // module date.js

基于WinDbg的内存泄漏分析

在前面C++中基于Crt的内存泄漏检测一 文中提到的方法已经可以解决我们的大部分内存泄露问题了,但是该方法是有前提的,那就是一定要有源代码,而且还只能是Debug版本调试模式下.实际上很 多时候我们的程序会用到第三方没有源代码的模块,有些情况下我们甚至怀疑系统模块有内存泄露,但是有没有证据,我们该怎么办? 这时我们就要依靠无所不能的WinDbg了. WinDbg的!heap命令非常强大,结合AppVerifier可以对堆(heap)内存进行详细的跟踪和分析, 我们接下来对下面的代码进行内存泄漏的

View的post方法导致的内存泄漏分析

简述: 写这篇文章的缘由是最近项目中查内存泄漏时,发现最终原因是由于异步线程调用View的的post方法导致的. 为何我会使用异步线程调用View的post方法,是因为项目中需要用到很多复杂的自定义布局,需要提前解析进入内存,防止在主线程解析导致卡顿,具体的实现方法是在Application启动的时候,使用异步线程解析这些布局,等需要使用的时候直接从内存中拿来用. 造成内存泄漏的原因,需要先分析View的post方法执行流程,也就是文章前半部分的内容 文章内容: View#post方法作用以及实

android 内存泄漏分析技巧

java虚拟机运行一般都有一个内存界限,超过这个界限,就会报outofmemory.这个时候一般都是存在内存泄漏.解决内存泄漏问题,窃以为分为两个步骤:分析应用程序是否真的有内存泄漏,找到内存泄漏的地方.这两个步骤都不是一般意义上的调试,直接打log,断点调试都不是太给力.动脑筋想一想,内存问题应该在很多地方上都会出现,这么常见的问题应该是有工具的.android现在更可以说是一个生态系统,当然也有很多开发辅助工具.在前面的两个步骤中都有很强大的武器,熟练的掌握这些利器,分析问题就会事半功倍.

使用Eclipse Memory Analyzer进行内存泄漏分析三部曲

源地址:http://seanhe.iteye.com/blog/898277 一.准备工作 分析较大的dump文件(根据我自己的经验2G以上的dump文件就需要使用以下介绍的方法,不然mat会出现oom)需要调整虚拟机参数 找个64位的系统在MemoryAnalyzer.ini设置-Xmx2g 如果是32位的xp可以使用下面的方法进行尝试: 安装jrockit 6.0的JDK mat使用jrockit的jdk来启动 Java代码   -vm D:/Program Files/Java/jroc

Java内存泄漏分析与解决方案

Java内存泄漏是每个Java程序员都会遇到的问题,程序在本地运行一切正常,可是布署到远端就会出现内存无限制的增长,最后系统瘫痪,那么如何最快最好的检测程序的稳定性,防止系统崩盘,作者用自已的亲身经历与各位网友分享解决这些问题的办法. 作为Internet最流行的编程语言之一,Java现正非常流行.我们的网络应用程序就主要采用Java语言开发,大体上分为客户端.服务器和数据库三个层次.在进入测试过程中,我们发现有一个程序模块系统内存和CPU资源消耗急剧增加,持续增长到出现java.lang.Ou

100%正确的内存泄漏分析工具 &#160; &#160; &nbsp; --------tMemMonitor (TMM)

C/C++由于灵活.高效的优点一直以来都是主流的程序设计语言之一,但是其内存的分配与释放均由程序员自己管理,当由于疏忽或错误造成程序未能释放不再使用的内存时就会造成内存泄漏.在大型.复杂的应用程序中,内存泄漏往往是最常见的问题,因而及时解决内存泄漏非常必要.tMemMonitor (TMM)作为一个专业.准确.易用的内存泄漏分析工具,可以帮助C/C++程序员迅速地解决内存泄漏这个令人头疼的问题. TMM下载地址: 一.开发背景 目前市面上已有一些Windows平台下的内存泄漏动态检测工具,比如U