这期博客的话题有些沉重,我们来讨论.net对象的生生死死。首先,要给生死下个定义。在这篇博客中,每当谈及一个对象是死了的对象,指的是用户无法再获得其引用。这个定义是个对用户友好的定义,因为有很多时候,对象还残存在托管堆上,CLR依旧可以通过一些手法来获得它(比如RCW缓存中通过SyncBlk),但是这种“生不如死”的状态不在今天的讨论范围之内。

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

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

Code

程序的运行结果是

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可以使对象复生。