一、什么是内存泄露(memory leak)?

内存泄露不是指内存坏了,也不是指内存没插稳漏出来了,简单来说,内存泄露就是在你期待的时间内你程序所占用的内存没有按照你想象中的那样被释放。

因此什么是你期待的时间呢?明白这点很重要。如果一个对象占用内存的时间和包含这个对象的程序一样长,但是你并不期望是这样。那么就可以认为是内存泄露了。用具体例子来说明如下:

class Button {
  public void OnClick(object sender, EventArgs e) {
    ...
  }
}
class Program {
  static event EventHandler ButtonClick;
  static void Main(string[] args) {
      Button button = new Button();
      ButtonClick += button.OnClick;
  }
}

上面这段代码中,我们使用了一个静态的事件,而静态成员的生命周期是从AppDomain被加载开始,直到AppDomain被卸载,也就是说在通常情况下如果进程没被关闭,又忘记取消注册事件,那么ButtonClick事件包含的EventHandler委托所引用的对象会一直存在到进程结束为止,这就造成了内存泄露问题。这也是.NET中最常见的内存泄露问题的原因之一。后面我会接着说怎么解决这种事件造成的泄露问题。

二、内存回收的方式

1、引用计数

引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个 变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办 法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为零的值所占用的内存。

像原来IE6中Javascript中原生对象内存回收的方式就是通过检查对象是否有引用来判断一个对象是否是垃圾。IE9之前,其BOM和DOM中的对象是使用C++以COM对象的形式实现的,而COM对象的垃圾收集机制采用的也是引用计数策略。而这种方式通常会因为循环引用导致内存泄露,也就是A引用B的同时,B也引用者A。 在Objective-C中也会有这样的循环引用的问题。在Objective-C中的解决方案就是给一方标记为weak,介绍可以参看这里,关于Objective-C中的委托模式的介绍。

2、标记清除法(mark-weep)

C#中采用的是标记法回收内存,全部对象都要标记,并且只标记一次就不再标记。判断一个对象是不是垃圾取决于是否有引用,而是取决是是否被root引用。

root的类型有寄存器中的变量,线程栈上的变量,静态变量等。

我们来看一幅通常情况下的对象图,图中有一个循环引用。

我们抽取其中一部分图说明

在采用标记清除策略的实现中,由于函数执行之后,local3出栈,离开了作用域,因此这种相互引用在标记清除法中不是个问题。

我们很容易看出,因为每一个对象都要mark,因此创建大量的小对象会给Mark阶段造成压力。值得注意的是,在GC的mark和weep阶段,会挂起所有线程,因此创建大量的线程也是会对GC造成问题。这个问题我以后会再讨论。

三、弱引用解决一些问题

如前面所说,忘记取消注册事件通常是.NET中最常见的内存泄露问题,我们怎么自动化的解决这个问题呢?也就是说当方法所属的对象已经被标记为垃圾的时候,我们就在事件中取消注册这个方法。这时就可以通过弱引用来实现。

委托的本质就是一个类,包含了几个关键属性:

1. 指向原对象的Target属性(强引用)。

2. 一个指向方法的ptr指针。

3. 内部维护着一个集合(delegate是以链表结构实现)。

因为.NET中的委托是强引用,我们要把它改成弱引用,我们可以抓住这个这些特征,创建一个自己的WeakDelegate类。

事件的本质就是一个访问器方法,和委托的关系类似于字段和属性,也就是控制外部对字段的访问。我们可以通过自定义add和remove方法来把外部的委托转换成我们自己定义的委托。

public class Button
{
    private class WeakDelegate
    {
        public WeakReference Target;
        public MethodInfo Method;
    }
    private List<WeakDelegate> clickSubscribers = new List<WeakDelegate>();
    public event EventHandler Click
    {
        add
        {
            clickSubscribers.Add(new WeakDelegate
            {
                Target = new WeakReference(value.Target),
                Method = value.Method
            });
        }
        remove
     {
          .....
       }
    }
    public void FireClick()
    {
        List<WeakDelegate> toRemove = new List<WeakDelegate>();
        foreach (WeakDelegate subscriber in clickSubscribers)
        {
       //第一个Target表示方法所属的对象,第二个Target表示这个对象是否被标记为垃圾,如果为null则表示为已经被标记为垃圾。
            object target = subscriber.Target.Target;
            if (target == null)
            {
                toRemove.Add(subscriber);
            }
            else
            {
                subscriber.Method.Invoke(target, new object[] { this, EventArgs.Empty });
            }
        }
        clickSubscribers.RemoveAll(toRemove);
    }
}

  弱引用还可以用来创建一个对象池,对象池就是通过管理少量的对象来减少内存和GC压力。我们可以通过强引用来表示对象池内最小的对象数量,通过弱引用来表示可以达到的最大的数量。

.NET中常见的内存泄露问题——GC、委托事件和弱引用的更多相关文章

  1. 谈谈.NET中常见的内存泄露问题——GC、委托事件和弱引用

    其实吧,内存泄露一直是个令人头疼的问题,在带有GC的语言中这个情况得到了很大的好转,但是仍然可能会有问题.一.什么是内存泄露(memory leak)?内存泄露不是指内存坏了,也不是指内存没插稳漏出来 ...

  2. 对开发中常见的内存泄露,GDI泄露进行检测

    对开发中常见的内存泄露,GDI泄露进行检测 一.GDI泄露检测方法: 在软件测试阶段,可以通过procexp.exe 工具,或是通过任务管理器中选择GDI对象来查看软件GDI的对象是使用情况. 注意点 ...

  3. JavaScript 中常见的内存泄露陷阱(摘)

    内存泄露是每个开发者最终都不得不面对的问题.即便使用自动内存管理的语言,你还是会碰到一些内存泄漏的情况.内存泄露会导致一系列问题,比如:运行缓慢,崩溃,高延迟,甚至一些与其他应用相关的问题. 什么是内 ...

  4. Android开发中常见的内存泄露案例以及解决方法总结

    1.单例模式引起的内存泄露 由于单例模式的静态特性,使得它的生命周期和我们的应用一样长,如果让单例无限制的持有Activity的强引用就会导致内存泄漏如错误代码示例: public class Use ...

  5. 分享.net常见的内存泄露及解决方法

    分享.net常见的内存泄露及解决方法 关于内存泄漏的问题,之前也为大家介绍过,比如:<C++中内存泄漏的检测方法介绍>,是关于C++内存泄漏的.今天为大家介绍的是关于.NET内存泄漏的问题 ...

  6. JavaScript 中 4 种常见的内存泄露陷阱

    了解 JavaScript 的内存泄露和解决方式! 在这篇文章中我们将要探索客户端 JavaScript 代码中常见的一些内存泄漏的情况,并且学习如何使用 Chrome 的开发工具来发现他们.读一读吧 ...

  7. C程序中常见的内存操作错误

    对C/C++程序员来说,管理和使用虚拟存储器可能是个困难的, 容易出错的任务.与存储器有关的错误属于那些令人惊恐的错误, 因为它们在时间和空间上, 经常是在距错误源一段距离之后才表现出来. 将错误的数 ...

  8. 【转】《深入理解计算机系统》C程序中常见的内存操作有关的典型编程错误

    原文地址:http://blog.csdn.net/slvher/article/details/9150597 对C/C++程序员来说,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构 ...

  9. 错误内存【读书笔记】C程序中常见的内存操作有关的典型编程错误

    题记:写这篇博客要主是加深自己对错误内存的认识和总结实现算法时的一些验经和训教,如果有错误请指出,万分感谢. 对C/C++程序员来讲,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构成的 ...

随机推荐

  1. iOS 创建一个可以点击并拖拽的Button

    HSCButton.h #import <UIKit/UIKit.h> @interface HSCButton : UIButton { CGPoint beginPoint; } @p ...

  2. sql中去除重复的项

    方法一:group by  (取最小的id)select min(id) id,T from Table_1 group by T 方法二:union (不需要id)select T from Tab ...

  3. smarty缓存控制

    第一步初始化配置文件中设置 如果当前访问的模板有缓存就不需要连接数据库那些代码了,如果要模板局部不缓存,要写在iscache外,模板中用{nocache}

  4. 转! java 中“==” 与“ .equals ”比较

    在java程序设计中,经常需要比较两个变量值是否相等.例如1.简单数据类型比较a = 10;b = 10;if(a == b){//写要执行的代码}2.引用数据类型比较ClassA a = new C ...

  5. SQL技术内幕二DDL

    创建数据库: if db_id('DBTest') is nullcreate database DBTest 创建表 use eb_fy_data_test---use 切换所在数据库上下文 if ...

  6. IOS中http请求使用cookie

    http://rainbird.blog.51cto.com/211214/805173 IOS中http请求使用cookie 2012-03-13 23:04:30 标签:http 记录 龙的传人  ...

  7. c++学习笔记(c++中的引用)

    1.c++中的bool类型:     其实c语言中也有bool类型,如果是遵守c90标准的编译器(其实现在大量编译器都是c90标准的),对于bool类型的使用除了要使用头文件 stdbool.h外,与 ...

  8. 【Alpha】——Second Scrum Meeting

    一.今日站立式会议照片 二.每个人的工作 成员 昨天已完成的工作 今天计划完成的工作 李永豪 完成登录按钮代码 完成添加功能 郑靖涛 完成登录按钮代码 完成删除功能 杨海亮 完成注册按钮代码 完成查找 ...

  9. mysql My SQL获取某个表的列名

    My SQL获取某个表的列名 DESC TableName SHOW COLUMNS FROM TableName SELECT COLUMN_NAME  FROM information_schem ...

  10. Python之TabError: inconsistent use of tabs and spaces in indentation和ModuleNotFoundError:No module named &#39;win32api&#39;

    1.TabError: inconsistent use of tabs and spaces in indentation 这是我的代码,感觉没啥不对, 后来运行之后出现了下面的错误,我也是弄了好久 ...