Finalizer线程对Object生命周期的影响

众所周知,.NET倚仗GC管理分配在托管堆上的对象(也就是new出来的东东)。为了提供类似c++中析构函数的功能,也就是在对象即将死去的时候,执行一段用户代码来做一些清理工作,比如在一个COM组件上调用它的Release方法。

出于性能的考虑,CLR使用一个独立的线程来执行对象的Finalize方法,所以Finalize方法的执行并不是GC.Collect的一部分。下面一个程序验证了这个说法。


using System;
using System.Threading;
class ObjectWithFinalizer
{
   
~ObjectWithFinalizer()
    {
        Thread.Sleep(
1000);
        Console.WriteLine(
"Finalize in thread {0}", Thread.CurrentThread.ManagedThreadId);
    }
}

class Program
{
   
public static void Main()
    {
        Console.WriteLine(
"Run in thread {0}", Thread.CurrentThread.ManagedThreadId);
        ObjectWithFinalizer owf
= new ObjectWithFinalizer();
        GC.Collect();
        Console.WriteLine(
"GC.Collect() end");
    }
}

程序的运行结果是

Run in thread 1
GC.Collect() end
Finalize in thread 2

对CLR的行为略作解释。当GC发生的时候,CLR会遍历每个线程(也就是在每个线程上执行GC的相关算法),找出在当前执行点之后再也没有被引用的Object,把他们视为死了的对象。然后,对那些有finalize方法的对象,把他们放到专门用来执行Finalize方法的线程(我们称为Finalizer线程),并逐一执行之Finalize方法。这里要注意的是,GCCollect方法和Finalize线程的执行并不是同一个概念。其联系是:GC驱使了Finalize线程的执行。然而,GC的结束并不意味着Finalize阶段的结束。所以如果要同步主线程和Finalzer线程的执行,我们要一个专门的API,GC.WaitForFinalization(下面会有一个例子)

好奇的读者可能会问,假设GC结束的时候,有一个"死"了的Object的Finalize方法还没有被调用,那么他到底是死了还是活着?答案是,他是活者的,因为按照一开始给出的定义,在Finalizer线程中仍然可以引用到这个object。为了验证这个说法,我们用WeakReference来观察。WeakReference是一类特别的引用,普通的引用可以延长object的生命周期,而WeakReference则不能。举一个例子来说。

Code

程序的执行结果是

owf 1 is finalized
Finalize phase is ended
ObjectWithFinalizer
System.WeakReference
owf 2 is finalized

GC.Collect()被调用的下方,owf2和wr都被引用。但是当GC及其引发的Finalize线程结束的时候,owf已经死了,说明wr对其的引用并不能延续它的生命。当一个object死去之后,WeakReference会被自动设置成null,这个行为使得它成为我们探索对象生命周期时绝佳的跟踪器。

在给出跟踪器的例子之前,最后要介绍的是两种类型的WeakReference:第一类是Short WeakReference,它不能跟踪到finalizer线程里的对象,也就是说当GC把一个对象放到Finalizer线程的时候,它就已经被置null了;可以跟踪到的称之为long weakReference。对于同一个对象的引用,long weakReference的跟踪范围比short WeakReference更长。产生short/long weakReference的方法在于构造函数里的一个参数WeakReference(Object o,bool b),当b为true时,产生long WeakReference,否则产生Short WeakReference。默认是false。

好了,下面的这段代码是这篇博客的精华,先描述一下整体思路,具体解释参见代码中的注释。 我们创建了三个不同类型的对象,并且通过跟踪器观察在GC.Collect, GC.WaitForPendingFinalizer前后他们是死是活。

Code

值得一说的是,当Finalize线程结束工作的时候,并不会把那些Long WeakReference置null,所以仍然发现两个Object在Finalizer里面是活的,必须等到再一次GC,才能把它们收集。

那么,如果在第二次GC之前,把这个WeakReference重新赋给一个普通的对象,会发生什么事情呢?下一个例子给出了解释:

Code

输出结果如下:

Hello Hell:)
Hello World!

在这个例子中,ObjectFromHell已经被Finalize了,但是我们依然可以通过LongWeakReference来使他复生(Resurrect)。(强烈不推荐使用这种方法来操纵Object。。。)

总结一下今天讲的东西:

1. GC把有Finalize方法的对象放到一个单独的Finalizer线程中执行他们的Finalize方法。

2. 在主程序中如果要等待Finalizer线程结束,需要显示调用GC.WaitForPendingFinalizer方法。

3. 之后需要再调用一次GC,才能把Finalizer线程里面的垃圾收集。

4. 用WeakReference可以跟踪对象,shortWeakRefence跟踪到Finalize之前,LongWeakReference跟踪到Finalize里面。

5. 使用LongWeakReference可以使对象复生。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值