C#内存泄漏--event内存泄漏

内存泄漏是指:当一块内存被分配后,被丢弃,没有任何实例指针指向这块内存, 并且这块内存不会被GC视为垃圾进行回收。这块内存会一直存在,直到程序退出。C#是托管型代码,其内存的分配和释放都是由CLR负责,当一块内存没有任何实例引用时,GC会负责将其回收。既然没有任何实例引用的内存会被GC回收,那么内存泄漏是如何发生的?

内存泄漏示例

  为了演示内存泄漏是如何发生的,我们来看一段代码

复制代码

class Program

{

static event Action TestEvent;

static void Main(string[] args)

{

var memory = new TestAction();

TestEvent += memory.Run;

OnTestEvent();

memory = null;

//强制垃圾回收

GC.Collect(GC.MaxGeneration);

Console.WriteLine("GC.Collect");

//测试是否回收成功

OnTestEvent();

Console.ReadLine();

}

public static void OnTestEvent() {

if (TestEvent != null) TestEvent();

else Console.WriteLine("Test Event is null");

}

class TestAction
{
public void Run() {
Console.WriteLine("TestAction Run.");
}
}

}

复制代码

  该例子中,memory.run订阅了TestEvent事件,引发事件后,会在屏幕上看到 TestAction Run。当memory =null 后,memory原来指向的内存就没有任何实例再引用该块内存了,这样的内存就是待回收的内存。GC.Collect(GC.MaxGeneration)语句会强制执行一次垃圾回收,再次引发事件,发现屏幕上还是会显示TestAction Run。该内存没有被GC回收,这就是内纯泄漏。这是由TestEvent+=memory.Run语句引起的,当GC.Collect执行的时候,当他看到该块内存还有TestEvent引用,就不会进行回收。但是该内存已经是“无法到达”的了,即无法调用该块内存,只有在引发事件的时候,才能执行该内存的Run方法。这显然不是我想要的效果,当memory = null执行时,我希望该内存在GC执行时被回收,并且当TestEvent被引发时,Run方法不会执行,因为我已经把该内存“解放”了。

  这里有一个问题,就是C#中如何“释放”一块内存。像C和C++这样的语言,内存的声明和释放都是开发人员负责的,一旦内存new了出来,就要delete,不然就会造成内存泄漏。这更灵活,也更麻烦,一不小心就会泄漏,忘记释放、线程异常而没有执行释放的代码...有手动分配内存的语言就有自动分配和释放的语言。最开始使用垃圾回收的语言是LISP,之后被用在Java和C#等托管语言中。像C#,CLR负责内存的释放,当程序执行一段时间后,CLR检测到垃圾内存已经值得进行一次垃圾回收时,会执行垃圾回收。至于如何判定一块内存是否为垃圾内存,比较著名的是计数法,即有一个实例引用了该内存后,就在该内存的计数上+1,改实例取消了对该内存的引用,计数就-1,当计数为0时,就被判定为垃圾。该种方法的问题是对循环引用束手无策,如A的某个字段引用了B,而B的某个字段引用了A,这样A和B的技术都不会降到0。CLR改用的方法是类似“标记引用法”(我自己的命名):在执行GC时,会挂起全部线程,并将托管堆中所有的内存都打上垃圾的标记,之后遍历所有可到达的实例,这些实例如果引用了托管堆的内存,就将该内存的标记由垃圾变为被引用。当遇到A和B相互引用的时候,如果没有其他实例引用A或者B,虽然A和B相互引用,但是A和B都是不可到达的,即没办法引用A或者B,则A和B都会被判定为垃圾而被回收。讲解了这么一大堆,目的就是要说,在C#中,你想要释放一块内存,你只要让该块内存没有任何实例引用他,就可以了。那么当执行memory = null后,除了对TestEvent的订阅,没有任何实例再引用了该块内存,那么为什么订阅事件会阻止内存的释放?

  我们来看看TestEvent+=memory.Run()这句话都干了什么。我们利用IL反编译上面的dll,可以看到

复制代码

1 IL_0000: nop

2 IL_0001: newobj instance void EventLeakMemory.Program/TestAction::.ctor()

3 IL_0006: stloc.0

4 IL_0007: ldloc.0

5 IL_0008: ldftn instance void EventLeakMemory.Program/TestAction::Run()

6 IL_000e: newobj instance void [mscorlib]System.Action::.ctor(object, native int)

7 IL_0013: call void EventLeakMemory.Program::add_TestEvent(class [mscorlib]System.Action)

...//其他部分

复制代码

  关键在5-7行。第5和6行,声明了一个System.Action型的委托,参数为TestAction.Run方法,第七行,执行了Program.add_TestEvent方法,参数是上面声明的委托。也就是说+=操作符相当于执行了Add_TestEvent(new Action(memory.Run)),就是这个new Action包含了对memory指向的内存的引用。而这个引用在CLR看来是可达的,可以通过引发事件来调用该内存。

解决办法

  我们已经找到了内存泄漏的元凶,就是订阅事件时,隐式声明的匿名委托对内存的引用。最简单的解决办法是手动取消订阅事件,只要TestEvent -= memory.Run就可以了。但如何实现一个不需要手动取消订阅的事件?该问题的解决办法是使用一种和普通的引用不同的方式来引用方法的实例对象:该引用不会影响垃圾回收,不会在GC时被判定为对该内存的引用,也就是“弱引用”。C#中,绝大部分的类型都是强引用。如何实现弱引用?来看一个例子:

复制代码

static void Main(string[] args){

var obj = new object();

var gcHandle = GCHandle.Alloc(obj, GCHandleType.Weak);

Console.WriteLine("gcHandle.Target == null is :{0}", gcHandle.Target == null);

obj = null;

GC.Collect();

Console.WriteLine("GC.Collect");

Console.WriteLine("gcHandle.Target == null is :{0}", gcHandle.Target == null);

Console.ReadLine();

}

复制代码

  当执行GC。Collect后,gcHandle.Target == null 由false 变成了true。这个gcHandle就是obj的一个弱引用。这个类的详细介绍见 GCHandle 。比较关键的是GCHandle.Alloc方法的第二个参数,该参数接受一个枚举类型。我使用的是GCHandleType.Weak,表明该引用是个弱引用。利用这个方法,就可以封装一个自己的WeakReference类,代码如下:

  我实现了IEquatable接口,该接口能方便的比较WeakReference实例和委托是否指一个方法。利用该类,就可以写一个自己的弱事件封装器。

  最后,就可以像下面这样定义自己的事件了

复制代码

public class TestEventClass {

private WeakEventManager<Action<object, EventArgs>> _testEvent = new WeakEventManager<Action<object, EventArgs>>();

public event Action<object, EventArgs> TestEvent {

add { _testEvent.AddHandler(value); }

remove { _testEvent.RemoveHandler(value); }

}

protected virtual void OnEvent(EventArgs e) {
_testEvent.Raise(this, e);
}

}

复制代码

  这里,要感谢@delowly网友的提醒,告诉我代码有错误,达不到效果。这个简易的弱事件是改版的,老版本由于我没有测试代码,就放到了文章中,造成了错误,对此深表歉意。我保证以后所有的文章中写的代码都要经过反复的测试。

event内存泄漏的更多相关文章

  1. event 内存泄漏

    组长说用event有内存泄漏的隐患..做个测试. 预留

  2. C#内存泄漏--event内存泄漏

    内存泄漏是指:当一块内存被分配后,被丢弃,没有任何实例指针指向这块内存, 并且这块内存不会被GC视为垃圾进行回收.这块内存会一直存在,直到程序退出.C#是托管型代码,其内存的分配和释放都是由CLR负责 ...

  3. Android性能优化之利用Rxlifecycle解决RxJava内存泄漏

    前言: 其实RxJava引起的内存泄漏是我无意中发现了,本来是想了解Retrofit与RxJava相结合中是如何通过适配器模式解决的,结果却发现了RxJava是会引起内存泄漏的,所有想着查找一下资料学 ...

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

    系列主题:基于消息的软件架构模型演变 在Winform和Asp.net时代,事件被大量的应用在UI和后台交互的代码中.看下面的代码: private void BindEvent() { var bt ...

  5. 浅析c#内存泄漏

    一直以来都对内存泄露和内存溢出理解的不是很深刻.在网上看到了几篇文章,于是整理了一下自己对内存泄露和内存溢出的理解. 一.概念 内存溢出:指程序在运行的过程中,程序对内存的需求超过了超过了计算机分配给 ...

  6. Release编译模式下,事件是否会引起内存泄漏问题初步研究

    题记:不常发生的事件内存泄漏现象 想必有些朋友也常常使用事件,但是很少解除事件挂钩,程序也没有听说过内存泄漏之类的问题.幸运的是,在某些情况下,的确不会出问题,很多年前做的项目就跑得好好的,包括我也是 ...

  7. js内存泄漏

    IE和webkit浏览器都是采用计数来处理垃圾,也就是说每个对象被引用一次,该对象的计数器成员+1,如果计数器为0,那么这个对象被销毁 例如: function A() { var obj = {}; ...

  8. UWP开发入门(十六)——常见的内存泄漏的原因

    本篇借鉴了同事翔哥的劳动成果,在巨人的肩膀上把稿子又念了一遍. 内存泄漏的概念我这里就不说了,之前<UWP开发入门(十三)——用Diagnostic Tool检查内存泄漏>中提到过,即使有 ...

  9. UWP开发入门(十三)——用Diagnostic Tool检查内存泄漏

    因为.NET的垃圾回收机制相当完善,通常情况下我们是不需要关心内存泄漏的.问题人一但傻起来,连自己都会害怕,几个页面跳啊跳的,内存蹭蹭的往上涨,拉都拉不住.这种时候我们就需要冷静下来,泡一杯热巧克力. ...

随机推荐

  1. Codeforces Round #316 (Div. 2)E. Pig and Palindromes DP

    E. Pig and Palindromes   Peppa the Pig was walking and walked into the forest. What a strange coinci ...

  2. Linux下安装intellij idea

    1.下载 http://www.jetbrains.com/idea/download/#section=linux 我下载的是不带jdk的版本 2.放入opt目录中 3.解压到usr下面的intel ...

  3. QT-自定义信号和槽

    前言:信号和槽是一种松耦合机制,或者说是一种分布式机制,信号广播出去,槽会自定义订阅接收. 一.新建工程 二.新建部件 拖入button按钮.修改内容为“发送自定义信号” 三.自定义发送信号 3.1 ...

  4. CLR - 基础

    前言 好记性不如烂“笔头”系列... 目录 托管模块 JIT(just-in-time) 元数据 CLR 解析类型引用 托管模块 面向 CLR 的编译器在编译源文件时最终会编译成一个 PE(可移植执行 ...

  5. GradientDrawable类的利用动态设置样式中的颜色

    1.xml样式文件 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android=& ...

  6. [ OS ][ Linux ] [ SA ] root 帳號名稱修改

    1. 背景: 由於 Linux 中預設最高權限的使用者一律為 root, 存在安全性的疑慮. 所以比較安全的作法要將 root 帳號改名為其他名稱,讓要入侵的人需要同時破解出帳號和密碼才可以進入主機, ...

  7. windows 2008 中IIS7.0以上如何设置404错误页面

    404错误页面的设置,不仅仅可以提高用户体验度,从SEO方面考虑,也是非常重要的.今天,笔者在这里介绍一下在windows 2008下如何设置404错误页面. 注意:设置404有我这里介绍2种方式,推 ...

  8. hook的本质就是在本原可执行文件中加东西

    hook的本质就是在本原可执行文件中加东西. 本质就是添加东西:

  9. ABBYY FineReader 双十二特惠活动正在进行中...

    转眼间11月已悄然飘过,有些童鞋还没缓过双十一的劲,势必将剁手进行到底.只为当时没有鼓足勇气.狠下心来而悔恨其中.别担心,双十二你准备好了么,ABBYY FineReader 系列产品低价让你继续嗨到 ...

  10. 路飞学城Python-Day22