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

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

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

所以什么是你期待的时间呢?明白这点很重要。若是一个对象占用内存的时间和包含这个对象的程序同样长,可是你并不指望是这样。那么就能够认为是内存泄露了。用具体例子来讲明以下:函数

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中最多见的内存泄露问题的缘由之一。后面我会接着说怎么解决这种事件形成的泄露问题。this

2、内存回收的方式

一、引用计数线程

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

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

二、标记清除法(mark-weep)htm

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

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

咱们来看一幅一般状况下的对象图,图中有一个循环引用。生命周期

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

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


咱们很容易看出,由于每个对象都要mark,所以建立大量的小对象会给Mark阶段形成压力。值得注意的是,在GC的mark和weep阶段,会挂起全部线程,所以建立大量的线程也是会对GC形成问题。这个问题我之后会再讨论。

3、弱引用解决一些问题

如前面所说,忘记取消注册事件一般是.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压力。咱们能够经过强引用来表示对象池内最小的对象数量,经过弱引用来表示能够达到的最大的数量。

相关文章
相关标签/搜索