心如止水

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

1. 规则:允许 Dispose 方法被调用多次而不引发异常。 此方法在首次调用后应该什么也不做。

2. using 语句允许程序员指定使用资源的对象应当何时释放资源。为 using 语句提供的对象必须实现 IDisposable 接口,此接口提供了 Dispose 方法,该方法将释放此对象的资源。

3. Close和Dispose

In most .NET framework classes, there is no difference between Close() and Dispose(). For example, these methods do the same thing in the System.IO.Stream class, and it doesn't matter which of the two methods you call. You should call one but not both.
Stream的close和dispose方法:
public void Dispose()
{
    this.Close();
}
public virtual void Close()
{
    this.Dispose(true);
    GC.SuppressFinalize(this);
}

There are exceptions,for example, System.Data.SqlClient.SqlConnection has different behavior for Close() and Dispose().
Only call the Close method on a database connection if the object will be reused. Otherwise, use the Dispose method. 在 SqlConnection 中 Close 只是表示关闭数据库连接,并没有释放 SqlConnection 这个对象资源。

4. Finalize 和 Dispose 释放的资源应该相同(除托管资源外),这样即使类使用者在没有调用 Dispose 的情况下,资源也会在 Finalize 中得到释放。

 

You should not implement a Finalize method for managed objects, because the garbage collector cleans up managed resources automatically.
Furthermore, the garbage collector has no knowledge of unmanaged resources such as window handles, or open files and streams and database connections.

 

5. 比如这个问题:

void Func()
{
  A a = new A();
  B b = new B();
  a.RefToB = b;
  b.RefToA = a;
}

那么a和b会不会被GC回收?好几个人都答错。如果你按照COM的模式去思考GC,那就完全错误了。每次我问“什么情况下,对象会被GC回收?”,他们都能回答上来“当程序里没有对对象的引用时”。但是错了,为什么?如果你还没明白,就再看看上面这段代码。

GC 管理对象不是用的COM的引用计数模式。事实上最初微软确实想用引用计数方式实现GC,这样的一个优势就是对象的析构时机是确定的,当引用计数为0时,对象会被析构,之后也不会再有任何代码能够再访问该对象,这是很理想的情况。但经过反复实验,这种方法被抛弃了。一个原因就是如上的例子,会导致对象无法释放。另一个重要原因就是应用计数的额外开销对高性能程序不可接受。尤其是在多线程情况下,因为.NET使用自由线程模型,多个线程可能同时访问一个对象,每一个引用计数的增减操作都不得不做线程同步。

.NET采用的GC模式是分代GC(Generational GC)。那么再回答开始的问题,“什么情况下,对象会被GC回收?”正确答案是“当程序里没有对对象的活引用(Alive reference)时。”

6. 引用对象会被默认分配到GC Heap的SOH(Small Object Heap)上,超过85KB对象会被分配到LOH(Large Object Heap)。至于85K这个Magic Number的由来,应该是编写.Net框架的工程师们经过平时各种使用场景和优化推算出来的,我们不必多费心研究。

GC Heap

对于LOH来说,清理垃圾的过程等于“回收+归并”,即不仅要清理掉内存空间,还要将尚存的对象向着一个方向移动直到和之前的对象紧紧贴在一起[1][3]。如下所示,SOH上有四个对象:

[obj1][    obj2     ][  obj3  ][obj4]
↑
0代指针(p0)

经过一次清理后,obj2和obj4没了,留下来的都是1代对象,并将0代指针移动到内存低位的第一个空闲空间处:

[obj1][  obj3  ][obj5]
↑               ↑
p1              p0

2代对象的生成类似,这样的移动保证不会产生大量的内存碎片,最大程度提高了内存使用效率。

可惜对于大对象来说,移动的成本过高,所以会优先采用查找可用剩余碎片空间的方式,将可以容纳新对象的空间重复利用。类似上面的内存情况在0代清理、新分配了obj5后会变成:

[obj1][obj5]         [  obj3  ]

obj5和obj3之间的内存如果小于85K就很可惜,因为再也利用不起来了。

需要时时留意的是,直到.Net 4.0中也不会对大对象堆做碎片整理操作,但是这个只是一个CLR的实现细节,程序代码不应该依赖这个特点。如果要确保对象不会被垃圾回收器移动,就要使用fixed语句。

分代处理

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获得。

目前.Net的GC中支持的代数为2(GC.MaxGeneration),一般来说,可以将0-2这三代对象视作:

0代,大多数对象,最年轻的,GC进行首先回收的。(newly created objects are in G0)

2代,少数常驻内存的对象,例如asp.net和整个网站生存期等同的一些全局对象。2代对象就是最老的对象不会在提升代数。(if objects in G1 cannot be claimed, it is moved to G2)

1代,介于0代和2代之间的一个灰色地带。(if objects in G0 cannot be claimed, it is moved to G1)

在GC动作的时候,0代会首先会被回收,如果需要的内存空间不够用再依次处理1和2代对象。

对于程序中的引用对象,可以通过GC.GetGeneration()获取它的代数,这个方法还有一个重载版本支持WeakReference。

7. 尽量不要覆盖Finalize方法。原因无外乎两点:

空间损耗包含Finalize()的对象,需要GC两次处理才能回收。对象会被提升代数,获得更久的生存期。

时间损耗。

首先要了解与Finalize相关的两个队列:终止队列(Finalization Queue)可达队列(Freachable Queue)这两个队列存储了一组指向对象的指针。

  当程序中在托管堆上分配空间时(new),如果该类含有析构函数,GC将在Finalization Queue中添加一个指向该对象的指针。在GC首次运行时,会在已经被确认为垃圾的对象中遍历,如果Finalization Queue包含某个垃圾对象的指针GC将这个对象从垃圾中分离出来,将它的指针储存到Freachable Queue中,并在Finalization Queue删除这个对象的指针记录,这时该对象就不是垃圾了——这个过程被称为是对象的复生(Resurrection)。当Freachable Queue一旦被添加了指针之后,它就会去执行对象的Finalize()方法,清除对象占用的资源。

  当GC再次运行时,GC会首先检查Freachable队列中的对象是否可达,如果对象是不可达的话,对象会被回收掉。

  至此,通过GC两次运行,终于回收了带有析构函数的对象。

  复活实例:


 private void Form1_Load(object sender, EventArgs e) 
{
  Resource re 
= new Resource();   
  re 
= null;GC.Collect();
  GC.WaitForPendingFinalizers();
  
//首次GC.Collect()没起作用哦。 
  label1.Text = re.num.ToString();
}  
public class Resource
{
  
public int num;
  
~Resource()
  {
    。。。
  }

    看了上面的代码,大家应该了解什么是复活了吧!那么为什么要复生呢?因为首次GC时,这个对象的Finalize()方法还没有被执行,如果不经过复生就被GC掉,那么就连它的Finalize()一起回收了,Finalize()就无法运行了,所以必须先复生,以执行它的Finalize(),然后再回收。

  还有两个方法ReRegisterForFinalizeSuppressFinalize需要讲一讲,ReRegisterForFinalize是将指向对象的指针重新添加到Finalization Queue(即召唤系统执行Finalize()方法)SuppressFinalize是将对象的指针从Finalization Queue中移除(即拒绝系统执行Finalize()方法)

这就出现了一个很有趣的现象,因为在Finalization Queue中的对象可以复生,如果在对象的Finalize方法中调用ReRegisterForFinalize方法,这样就形成了一个在堆上永远不会死去的对象,像凤凰涅槃一样每次死的时候都可以复生。

8. one stack per thread, thus a process with N threads has N stacks.

参考:

http://www.cnblogs.com/sunshinefly128/archive/2011/09/11/2173628.html

http://www.cnblogs.com/isline/archive/2009/03/04/1402713.html

posted on 2012-04-22 22:57  cutebear  阅读(303)  评论(0编辑  收藏  举报