2010年2月16日 星期二 20时39分

 

 如下内容,是自己乱想的,不代表正确性的。。

 

.net中资源(相对于内存来说)分两种,托管资源和非托管资源。

非托管资源,有形如如下的几种:数据库、文件等

在.net中,托管资源由CLR控制,由GC(Garbage Collector)负责释放。非托管资源,一般需要实现IDisposable来负责实现资源的回收释放。


堆内存分配

到第一个有足够空间的内存地址(没被占用的),然后将该内存分配。

托管堆中内存的分配是顺序的,也就是说一个挨着一个的分配。这样内存分配的速度就要比原始程序高,但是高出的速度会被GC找回去


GC工作方式

托管代码中的对象什么时候回收我们管不了(除非用GC.Collect强迫GC回收)。GC会在它"高兴"的时候执行一次回收(这有许多原因,比如内存不够用时。这样做是为了提高内存分配、回收的效率)。那么如果用Destructor呢?同样不行,因为.NET中Destructor的概念已经不存在了,它变成了Finalizer,为什么呢?在.NET中由于GC的特殊工作方式,Destructor并不实际存在,事实上,当调用Destructor的语法时,编译器会自动将它写为protected virtual void Finalize();

GC步骤

1:确定对象没有任何引用
2:检查对象是否在Finalizer表上有记录(如果类定义的时候,提供了析构函数,则在实例化对象的时候,则会在Finalizer表中增加一条记录)
3:如果在Finalizer表上有记录,那么将记录移到另外的一张表上(Finalizer2)。
4:如果不在Finalizer2表上有记录,那么释放内存

对于第4步,Finalizer2中的记录,又是什么时候删除呢?
   在Finalizer2表上的对象的Finalizer会在另外一个low priority的线程上执行后从表上删除

从如上的步骤,可以看出,有析构函数的对象,不会马上被回收(代码1),只有执行一次以上的GC回收,才会被回收。

所以说,除非是绝对的需要,请不要提供析构函数。


代码1:

public class CountObject {
  public static int Count = 0;

  public CountObject() {
    Count++;
  }

  ~CountObject() {
    Count--;
  }
}

static void Main() {
  CountObject obj;
  for (int i = 0; i < 5; i++) {
    // GC.Collect();(就算执行 GC.Collect,也不会被删除)
    obj = null; // 这一步多余,这么写只是为了更清晰些!
   obj = new CountObject();
  }

  // Count不会是1,因为Finalizer不会马上被触发,要等到有一次回收操作后才会被触发。
  Console.WriteLine(CountObject.Count);
  Console.ReadLine();
}
 

GC为了提高回收的效率使用了Generation的概念,原理是这样的,第一次回收之前创建的对象属于Generation 0,之后,每次回收时这个Generation的号码就会向后挪一,也就是说,第二次回收时原来的Generation 0变成了Generation 1,而在第一次回收后和第二次回收前创建的对象将属于Generation 0。GC会先试着在属于Generation 0的对象中回收,因为这些是最新的,所以最有可能会被回收,比如一些函数中的局部变量在退出函数时就没有引用了(可被回收)。如果在Generation 0中回收了足够的内存,那么GC就不会再接着回收了,如果回收的还不够,那么GC就试着在Generation 1中回收,如果还不够就在Generation 2中回收,以此类推。Generation也有个最大限制,根据Framework版本而定,可以用GC.MaxGeneration获得。在回收了内存之后GC会重新排整内存,让数据间没有空格,这样是因为CLR顺序分配内存,所以内存之间不能有空着的内存。现在我们知道每次回收时都会浪费一定的CPU时间,所以,不推荐手动GC.Collect。
 

非托管资源的释放

IDisposable接口,按照.NET Framework的标准,所有有需要手动释放非托管资源的类都得实现此接口。

如下的代码,是一个经典的代码写法:

public class Base : IDisposable {
  public void Dispose() {
    this.Dispose(true);
    GC.SupressFinalize(this);(对象从Finalizer表去掉)
  }

  protected virtual void Dispose(bool disposing) {
    if (disposing) {
      // 托管类
    }
    // 非托管资源释放
  }

  ~Base() {
    this.Dispose(false);//为什么提供析构函数,是因为避免忘记手动调用Dispose方法。为什么不使用GC.SupressFinalize(this)呢?因为调用析构函数的时候,就已经把对象从Finalizer表去掉了,因为只有对象没有被任何引用的时候,才会调用析构函数的嘛。。
  }
}


总结
一个对象只当在没有任何引用的情况下才会被回收。
一个对象的内存不是马上释放的,GC会在任何时候将其回收。
一般情况下不要强制回收工作。
如果没有特殊的需要不要写Finalizer。
不要在Finalizer中写一些有时间逻辑的代码。
在任何有非托管资源或含有Dispose的成员的类中实现IDisposable接口。
当用胖对象时可以考虑弱引用的使用。

 

posted on 2010-04-05 23:35  颜昌钢  阅读(548)  评论(0编辑  收藏  举报