原贴:http://hi.baidu.com/toughguy/blog/item/d84daef8e48be50dd9f9fdb0.html
这几天对垃圾回收机制有了较为透彻的理解,在此与朋友们分享一下。首先,让我们来了解下什么是垃圾回收机制。
为了使程序员从跟踪内存使用的繁重任务中解脱出来,利用充分的时间去完成业务逻辑,因此产生了垃圾回收机制,虽然大多数的垃圾回收器都要求应用程序不停地暂停从而释放出不在使用的资源,但是.NET的垃圾回收器效率依然很高。
垃圾回收器的基本思想:
寻找不再使用的对象,将他们从内存中删除,并压实托管堆以释放不在使用的对象所占用的内存。在堆被压实之后,所有的对象引用都将被调整为指向对象新的存储位置。
垃圾回收机制的功能:用来管理托管资源和非托管资源所占用的内存分配和释放。
垃圾回收器的基本假定:
1. 最近被分配的内存空间的对象最有可能需要被释放。在方法执行时,通常需要为该方法所使用到的对象分配内存空间,搜索最近被分配的对象集合有助于花费最少的工作来释放尽可能多的空闲内存空间。
2. 生命期最长的对象需要释放的可能性最小。在通过几轮的垃圾回收后仍然存在的对象不大可能是那种能够在下一轮回收中被释放的临时对象,搜索这些内存块往往要进行大量的工作,却只能释放很小一部分的内存空间。
3. 同时分配内存的对象通常也会同时使用,将同时分配内存的对象的存储位置彼此相连有助于提高缓存性能,在垃圾回收时也往往是同一批处理或者是遵循后分配,先释放原则。
.NET 框架的垃圾回收器被称为分代的垃圾回收器( Generational Garbage Conllector ),也就是说被分配的内存分为三个类别(生存期等级),或者说分为三代,即0,1,2三代,对应的托管堆的初始化大小分别是256K,2M和10M。当垃圾回收器在发现改变托管堆的大小能够提高性能的话,会改变托管堆的大小。例如:当应用程序实例化了一些占用内存较少的对象时,而且这些对象所占用的资源能被很快回收的话,0代托管堆的大小会变为128K.相反,如果垃圾回收器发现所占用的资源不能被很快回收的话,会增加托管堆的大小,这时就是512k了。(注意:前提是:当垃圾回收器在发现改变托管堆的大小能够提高性能的话,才会改变托管堆的大小。)
最近被分配的内存空间的对象被放置在第0代,一般存储于二级缓存中,所以能对第0代中的对象实现快速存取。经过一轮垃圾回收后,仍然保留在第0代中的对象则被移进第1代中,再经过一轮垃圾回收后,仍然保留再第1代中的对象则被移进第2代中,第2代中包含了生存期较长的对象,这些对象至少经过了两轮回收。
现在来考虑一个问题,为什么要将本轮中没有被回收掉的对象移到下一代中呢?朋友们如果对上面的内容理解了,那么这个问题的也就迎刃而解了。是的,就是为了把存取速度较快的空间给置空,分配给最近实例化的对象,为了提高效率用来实现对象的快速读取的。
现在来看看托管堆上的内存分配问题:
当程序请求为某对象分配空间时,托管堆上的指针会指向下一个最近的可用的内存空间,如下图所示:
由此图我们可以清晰的知道托管堆的内存分配是线性分配的,
当然,这种分配方式的效率也是最高的。而普通的堆对于内存分配是基于内存块大小的,两个同时声明的对象在普通堆上被分配的位置可能相隔较远,于是降低了缓存的性能。所以,在一个不需要太多垃圾回收的应用程序中,托管堆的表现会优于传统的堆。
下面来学习使用终结器[ Finalize()方法]
Protected void Finalize( ) { Base.Finalize(); }
类提供一个终结器以在对象被销毁时执行,值得注意的是:当一个对象实现了Finalize方法,垃圾回收器会在它的终结列表(Finalization List)中加入一个指向该对象的指针(终结列表中包含的是等待终结的对象)。
等到此轮垃圾回收开始时,垃圾回收器会将该对象的引用置入终结列表,标识此对象为等待终结的对象,虽然此对象现在仍然存在,但是在应用程序中已经引用不到该对象了。垃圾回收器在此轮使用一个专用的线程,使终结列表中的对象执行终结器,执行完终结器的对象会被此对象标记为不再需要终结的对象,并从终结列表中除去。
终结完成后,对象所占用的资源将在下一轮垃圾回收中被回收。
终结顺序是不确定的,但是当某个对象被终结时,由此对象产生都应该已经被终结,大家肯定在想这是为什么吧?