c#学习笔记-.net垃圾回收机制

MSDN对垃圾回收机制的描述: 

“.NET Framework 的垃圾回收器管理应用程序的内存分配和释放。每次您使用 new 运算符创建对象时,运行库都从托管堆为该对象分配内存。只要托管堆中有地址空间可用,运行库就会继续为新对象分配空间。但是,内存不是无限大的。最终,垃圾回收器必须执行回收以释放一些内存。垃圾回收器优化引擎根据正在进行的分配情况确定执行回收的最佳时间。当垃圾回收器执行回收时,它检查托管堆中不再被应用程序使用的对象并执行必要的操作来回收它们占用的内存。”

通常情况下我们并不知道垃圾回收器什么时候回收系统垃圾,因为这是由.NET决定的,MSDN上是这样说的:

“在大多数情况下,垃圾回收器在确定执行回收的最佳时机方面更有优势。但是,在某些不常发生的情况下,强制回收可以提高应用程序的性能。当应用程序代码中某个确定的点上使用的内存量大量减少时,在这种情况下使用 GC.Collect 方法可能比较合适。例如,应用程序可能使用引用大量非托管资源的文档。当您的应用程序关闭该文档时,您完全知道已经不再需要文档曾使用的资源了。出于性能的原因,一次全部释放这些资源很有意义。有关更多信息,请参见 GC.Collect 方法。”

也就是说,正常情况下,我们不需要关注垃圾回收器何时回收垃圾,但是在某些情况下我们需要手动调用GC.Collect方法以进行强制垃圾回收。

让我们先来看看Collect是怎样工作的,当我们创建了一个引用类型的对象以后,CLR会检查托管堆上是否有足够的空间,如果有,就会在托管堆上非配一块内存来存放对象,我们所创建的对象分为两种情况--有终结器的对象和没有终结器的对象,一会会详细讲到。

当垃圾回收器确定执行垃圾回收的时候,会从托管堆中寻找不再使用的托管对象,当发现某一个对象没有任何引用指向它的时候,就认为该对象符合回收条件,进行回收。但是,托管堆上有那么多对象,垃圾回收器从哪里入手呢?要引入根的概念,根就是局部变量、静态变量等变量指向托管堆的CPU寄存器,在寻找的过程中垃圾回收器首先从根上的一个链开始,找出这个链上的变量所引用的托管堆对象,并将这个对象加入一个图中,再看这个对象是否引用了托管堆的其他对象,如果引用了,则将所引用的其他对象也加入到这个图中,以此类推,直到这个链结束为止,开始寻找下一个链,当根上的所有变量都遍历完以后,在托管堆上那些没有被加入图中的对象就被认为是符合回收条件的对象,要进行回收以释放内存。

刚才说的是没有实现Finalize方法的对象的回收,对于实现了Finalize方法的对象的回收有一些不同。

为什么要实现Finalize方法呢?

前面说过CLR只能管理托管堆中的对象,对于非托管资源,CLR并不知道怎样处理,当我们在某一个类中使用了非托管资源的时候,虽然CLR可以跟踪这些非托管对象的生存周期,但是它不知道如何清理这些资源,对于这些类型的对象,.NET Framework 提供 Object.Finalize 方法,但是Finalize方法不执行任何操作,要让垃圾回收器在回收对象的内存之前对对象执行清理操作,必须在类中重写 Finalize 方法。

二次垃圾回收机制

垃圾回收器使用名为“终止队列”的内部结构跟踪具有 Finalize 方法的对象,当我们创建了一个实现了Finalize方法的对象时,垃圾回收器会自动在终止队列中加入一个指向该对象的项,当垃圾回收器执行回收时,它只回收没有终结器的不可访问对象的内存。这时,它不能回收具有终结器的不可访问对象。它改为将这些对象的项从终止队列中移除并将它们放置在标为准备终止的对象列表中。该列表中的项指向托管堆中准备被调用其终止代码的对象。垃圾回收器为此列表中的对象调用 Finalize 方法,然后,将这些项从列表中移除。后来的垃圾回收将确定终止的对象确实是垃圾,因为标为准备终止对象的列表中的项不再指向它们。在后来的垃圾回收中,实际上回收了对象的内存。

看一个具体的例子。

运行一下看看结果

 

运行一下:

我们会发现,第二次垃圾收集并没有调用RefA的析构函数,这是因为在第一次垃圾收集中已经把RefA所引用的对象的项从终止队列中排除了,执行WaitForPendingFinalizers后又从准备终止对象列表中移除了,所以对其回收按照没有实现Finalize方法的对象执行,如果我们在“Test.RefA= this;"后面再加上一句,GC.ReRegisterForFinalize(Test.RefA);,

然后运行

 

我们会看到又执行了RefA的析构函数,GC.ReRegisterForFinalize(Test.RefA)的作用就是相当于告诉垃圾收集器,这是一个实现了Finalize方法的对象,应该把它加入终止队列当中,

为什么垃圾回收器不自动把它加入到终止队列当中呢?这是因为RefA在这里是“复活”,而不是定义一个对象,所以不会自动加入,如果我们想告诉垃圾回收器不要载调用RefA的析构函数怎么办?使用GC.SuppressFinalize(Test.RefA)请求系统不要调用指定对象的终结器。在“GC.ReRegisterForFinalize(Test.RefA);”这句后面再加上GC.SuppressFinalize(Test.RefA),我们会看到又不调用RefA的析构函数了。

实际上.NET的垃圾回收机制非常复杂,比如说还有“代”的概念,对于垃圾回收本人也只是简单了解,希望各位不吝赐教,欢迎批评指正!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值