C#Java一样是一款自带了垃圾回收功能的语言,在编程时不需时刻考虑对已分配对象的内存进行回收,而如C++这样没有垃圾回收功能的语言则需考虑对象是否已不可调用,要用delete关键字回收其内存。

前提

讲垃圾回收前需要明白的是对象引用之间的关系。我们在定义一个类(当然不是抽象类)之后,C#用new关键字来分配任意数量的对象,注意:new关键字返回的是一个“”上的引用,而不是真正对象本身,这个引用保存在内。垃圾回收是清除堆上的空间(包括压缩空的内存块)。在面向对象程序中需要用到很多对象,在C#中还分为值类型引用类型,这些对象往往在一个函数中,而当程序调用完此函数且不再调用此函数时,函数里调用时分配的对象便不再有意义,而它又占用部分内存空间,此时就需要对其占用的空间进行回收,被称为垃圾回收(当然垃圾回收并不局限于函数调用里对象占用的空间,还包括其他很多情况所占用的空间)。

generation

C#用generation(代)对对象进行分类(其实可以理解为一种标识),在.NET4.0后对象一般可分为3代(0代,1代,2代),将0代和1代合称为ephemeral generation(暂时代)。

  • 0代:从未被标记且将要回收的(新分配的)对象
  • 1代:上一次回收中没有被回收的对象
  • 2代:在上一次及以上的垃圾回收后仍未被回收的对象

垃圾回收器回收时首先检查所有第0代对象,标志这些对象且清理后若得到足够的空闲内存,任何没有被回收的对象则被升至1代;若仍需更多内存,则检查1代对象的“可访问性”并进行回收,没有被回收的1代对象则被升至2代。(这其中还涉及到一个应用程序根“application root”的概念,就不深入研究了)

.NET4.0之前进行垃圾回收时,对于暂时代的回收,会暂时挂起当前进程中所有活动线程,以确保垃圾回收中不会访问堆,通俗地将就是垃圾回收时会暂停程序以进行垃圾回收;通过专门的线程清理不在暂时代的对象,此时允许程序继续分配堆上的对象。

.NET4.0后,对于暂时代上的对象,将使用一个专用后台线程进行回收,非暂时代的对象使用后台垃圾回收。这种改进无疑将提升程序的性能,但需要指出的是垃圾回收本来就可以在无任何人工干预(编程实现)下执行其工作,这种改进对编程人员不是透明的,也无需更多了解。

System.GC

垃圾回收无需人为进行干预,但某些情况下可能会需要手动编程以使用垃圾回收:程序即将运行的代码不希望被垃圾回收中断;程序突然分配了很多对象,希望尽可能多的删除已获得的内存。垃圾回收的相关类主要是在mscorlib.dll中的System.GC类。此类中的函数较少,功能也很明确,就不详细说明,值得注意的有以下几点

  1. 手动强制垃圾回收(调用Collect()函数)时,应随后调用GC.WaitForPendingFinalizers()函数

  2. 垃圾回收是回收托管堆上的对象,当程序没有使用非托管资源时(如PInvoke服务或COM交互),不需(应该避免)在类中提供Finalize()方法

  3. 当程序使用了非托管资源,要释放这些资源,在程序中直接重写Finalize()方法将导致编译出错,而应该通过类的析构函数(称为终结器)来实现,并且在其中能做清理非托管资源的工作,而不操作其他任何托管对象

  4. 如果希望立即清除非托管资源,可在类的定义中实现IDisposable接口,在方法Dispose()中既可清理非托管资源,还能处理类中其他对象,在对象离开作用域前手动调用Dispose()方法(值得注意的是,并非是垃圾回收器调用Dispose(),用户在手动调用此方法时,对象仍在托管堆上,并可访问所有其他分配在堆上的对象)。为了保证Dispose()的调用,应将其放入一个try/finally逻辑中,若嫌麻烦,可用using关键字,它保证了在退出using块后,“使用”的对象将自动调用Dispose()方法。

  5. 综合3、4,可在类中既实现Finalize()(通过析构函数),又实现Dispose()(实现IDisposable接口)。用户在类中如此定义时,若用户记得调用Dispose(),则可调用GC.SuppressFinalize()以通知回收器跳过此对象。在类中使用此种方式实现两个方法可能会有部分代码重叠,微软提供了一个官方模板提供参考

class Example : IDisposable
{
    private bool disposed = false;
    public void Dispose()
    {
        cleanUp(true);    //true表示用户触发的清理过程
        GC.SuppressFinalize(this);
    }
    private void cleanUp(bool disposing)    //自定义的私有辅助方法
    {
        if(!this.disposed)
        {
            //释放托管资源
            //清理非托管资源
        }
        disposed = true;    //保证多次调用Dispose()而不出错
    }
    ~Example()
    {
        cleanUp(false);   //false表示GC出发的清理过程
    }
}