作者:cornfield
漫谈.Net中的自动垃圾收集(Garbage Collection)机制
一直以来,垃圾收集(Garbage Collection)在软件界的名声并不好。很多程序员认为垃圾收集做得不如自己来的直接,高效。这种说法有些时候是对的,一个精心为自己的特定程序设计定制的内存回收方法,肯定比为所有程序提供垃圾回收性能要高。但那对程序员要求甚高,一个项目下来花在内存回收的设计上的时间和精力是很可观的,而稍有不慎便会酿成灾难性的错误,技术再高超的程序员负担不起,整个现代软件工业也负担不起。把这样普遍而又繁重的任务交给系统处理,将程序员从中解脱出来专注于事务逻辑和系统功能的实现,已经成为软件业的共识。微软新推出的.Net平台架构便引入了自动垃圾回收机制,本文将详细解剖其中的原理,回答诸如垃圾回收怎样工作?怎样控制垃圾回收?什么时候需要控制垃圾回收?什么又是垃圾回收不能解决的?等等重要问题,为.Net平台下的系统开发人员提供设计时的参考。
要搞清楚.Net 运行时的垃圾回收机制,首先需要搞清楚.Net运行时内存分配的情况。.Net 运行时对受管资源(Managed Resource)采用对象引用的堆式分配(Heap Allocation)方法。这种分配方法大多数时候是非常快的。一个实际系统的内存总是有限的,当系统的剩余的可分配的内存资源不多时,.Net 运行时便会“预见”到下面的内存资源将可能不会满足下面的内存分配请求,于是它便会开始执行垃圾回收释放那些系统不再引用的内存资源。.Net垃圾回收器采用的是一种叫做“标志紧缩”(Mark and Compat)的算法。每当垃圾收集开始,.Net垃圾收集器从运行时目前的根对象(包括全局对象,本地对象,静态对象,CPU寄存器对象),开始寻找那些被根对象引用的所有对象,这些对象便是在垃圾收集时运行时正在应用的对象,除去这些,其他的受管运行时对象(Managed Runtime Object)便是系统不再使用的对象,于是便可以进行垃圾收集。所有引用的对象被向下拷贝到运行时受管堆(Runtime Managed Heap),同时修改它们的引用指针。需要指出的是,在.Net垃圾收集器移动引用对象和改变引用指针时,系统不能在这些对象上有任何操作,这一点由运行时的互斥机制来保证,无需程序员干涉。
遍历目前所有被引对象的耗费往往是巨大的,实际上也不必这样做。一个经验的认识是,当一个对象在内存中驻留的时间越长,那么它越有可能继续被引用留在内存中。相反,一个对象在内存中驻留时间越短,它越有可能被收集。.Net收集器采用一种称作“代分”(Generation Division)的方法来体现这种经验的内存驻留理论。它把受管堆中的对象分成三代(Generations). 第一代是没有经历过垃圾收集驻留在内存中的对象,他们通常是一些局部变量,它们的生命最短。第二代是仅经历过一次垃圾收集仍然驻留在内存中的对象,它们通常是一些如表单,列表,按钮等生命较短的对象。第三代是经历过两次及两次以上的垃圾收集后仍然驻留在内存中的对象,它们通常是一些应用程序对象,它们往往要在内存中驻留很长时间。当运行时收集器开始执行垃圾收集时,它首先对第一代对象进行垃圾收集,这通常会释放较大的内存空间,往往会满足下面的内存请求。如果这一代的收集结果不理想,那么便会对第二代读像进行收集,同样如果还不理想,便进行第三代的垃圾收集。
.Net垃圾回收机制支持一种称作终止化(Finalizaion)的概念。这有点类似C++中的析构函数。终止化操作在垃圾收集执行后进行一些非受管资源的清除工作,它在.Net运行时里有很多限制,往往不被推荐实现。当程序员对一个对象实现了终止器(Finalizer)后,运行时便会将这个对象的引用加入一个称作终止化对象引用集的链表,作为要求终止化的标志。当垃圾收集开始时,若一个对象不再被引用但它被加入了终止化对象引用集的链表,那么运行时将此对象标志为要求终止化操作对象。待垃圾收集完成后,终止化线程便会被运行时唤醒执行终止化操作。显然这之后要从终止化对象引用集的链表中将之删去。容易看出来,终止化操作会给系统带来额外的开销。终止化是通过启用线程机制来实现的,这有一个线程安全的问题。.Net运行时不能保证终止化执行的顺序,也就是说如果对象A有一个指向对象B的引用,两个对象都有终止化操作,但对象A在终止化操作时并不一定有有效的对象A引用。所以.Net运行时不推荐对对象进行终止化操作,只是在有非受管资源如数据库的连接,文件的打开等需要严格释放时,才应用终止化操作。
大多数时候,垃圾收集应该交由.Net运行时来控制,但有些时候,可能需要人为地控制一下垃圾回收操作。例如在操作了一次大规模的对象集合后,我们确信不再在这些对象上进行任何的操作了,那我们可以强制垃圾回收立即执行,这通过调用System.GC.Collect() 方法即可实现,但频繁的收集会显著地降低系统的性能。还有一种情况,已经将一个对象放到了终止化对象引用集的链上了,但如果我们在程序中某些地方已经做了终止化的操作,在那之后便可以通过调用System.GC.SupressFinalize()来将对象的引用从终止化对象引用集链上摘掉,以忽略终止化操作。始终应该清楚的是,终止化操作的系统负担是很重的。
综上所述,.Net垃圾回收机制负责回收系统不再使用的受管内存资源,它通过一定的优化算法来选择收集的对象和时间。程序员只有在释放大量受管资源时可以进行立即强制垃圾收集,在释放非受管资源时采用终止化操作来处理,其它时间将资源的回收交由.Net垃圾收集起来做。
希望广大网友多多跟帖讨论!