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类。此类中的函数较少,功能也很明确,就不详细说明,值得注意的有以下几点
-
手动强制垃圾回收(调用
Collect()
函数)时,应随后调用GC.WaitForPendingFinalizers()
函数 -
垃圾回收是回收托管堆上的对象,当程序没有使用非托管资源时(如PInvoke服务或COM交互),不需(应该避免)在类中提供
Finalize()
方法 -
当程序使用了非托管资源,要释放这些资源,在程序中直接重写
Finalize()
方法将导致编译出错,而应该通过类的析构函数(称为终结器)来实现,并且在其中只能做清理非托管资源的工作,而不操作其他任何托管对象 -
如果希望立即清除非托管资源,可在类的定义中实现
IDisposable
接口,在方法Dispose()
中既可清理非托管资源,还能处理类中其他对象,在对象离开作用域前手动调用Dispose()
方法(值得注意的是,并非是垃圾回收器调用Dispose()
,用户在手动调用此方法时,对象仍在托管堆上,并可访问所有其他分配在堆上的对象)。为了保证Dispose()
的调用,应将其放入一个try/finally
逻辑中,若嫌麻烦,可用using
关键字,它保证了在退出using块后,“使用”的对象将自动调用Dispose()
方法。 -
综合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出发的清理过程
}
}