在这篇博客中,我将介绍GC(垃圾回收器)和finalizer线程的关系,讨论对象经历GC以及Finalizer线程调用其Finalize方法的生存期。在介绍了WeakReference(弱引用)之后,给出对象跟踪器的实现来验证生存周期的论述。最后,demo一个令对象复活的小程序。
这期博客的话题有些沉重,我们来讨论.net对象的生生死死。首先,要给生死下个定义。在这篇博客中,每当谈及一个对象是死了的对象,指的是用户无法再获得其引用。这个定义是个对用户友好的定义,因为有很多时候,对象还残存在托管堆上,CLR依旧可以通过一些手法来获得它(比如RCW缓存中通过SyncBlk),但是这种“生不如死”的状态不在今天的讨论范围之内。
言归正传。众所周知,.NET倚仗GC管理分配在托管堆上的对象(也就是new出来的东东)。为了提供类似c++中析构函数的功能,也就是在对象即将死去的时候,执行一段用户代码来做一些清理工作,比如在一个COM组件上调用它的Release方法。
出于性能的考虑,CLR使用一个独立的线程来执行对象的Finalize方法,所以Finalize方法的执行并不是GC.Collect的一部分。下面一个程序验证了这个说法。
Code
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
using System;
using System.Threading;
class ObjectWithFinalizer
{
int m_int;
public ObjectWithFinalizer(int i)
{
m_int = i;
}
~ObjectWithFinalizer()
{
Thread.Sleep(1000);
Console.WriteLine("owf {0} is finalized", m_int);
}
}
class Program
{
public static void Main()
{
ObjectWithFinalizer owf = new ObjectWithFinalizer(1);
ObjectWithFinalizer owf2 = new ObjectWithFinalizer(2);
WeakReference wr = new WeakReference(owf);
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Finalize phase is ended");
Console.WriteLine(owf2);
Console.WriteLine(wr);
}
}
程序的执行结果是
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
using System;
using System.Threading;
using System.Collections.Generic;
/// <summary>
/// 该类型为每一个Object方法创建了ShortWeakReference, LongWeakReference
/// 并记录了object.ToString(),方便显示
/// </summary>
class ObjectRecord
{
string m_objString;
WeakReference m_shortWeakReference;
WeakReference m_longWeakReference;
public ObjectRecord(Object o)
{
m_objString = o.ToString();
m_longWeakReference = new WeakReference(o, true);
m_shortWeakReference = new WeakReference(o, false);
}
public override string ToString()
{
return m_objString;
}
public Object ObjectByShortWeakReference
{
get { return m_shortWeakReference.Target; }
}
public Object ObjectByLongWeakReference
{
get { return m_longWeakReference.Target; }
}
}
/// <summary>
/// 对象跟踪器
/// </summary>
class ObjectTracker
{
List<ObjectRecord> m_lstObjectRecord;
public ObjectTracker()
{
m_lstObjectRecord = new List<ObjectRecord>();
}
/// <summary>
/// 注册一个对象到跟踪器中
/// </summary>
/// <param name="o"></param>
public void Register(Object o)
{
m_lstObjectRecord.Add(new ObjectRecord(o));
}
/// <summary>
/// 显示注册到跟踪器的对象的生死:)
/// </summary>
public void ShowObjects()
{
foreach (ObjectRecord obj in m_lstObjectRecord)
{
Console.Write(obj);
if (obj.ObjectByShortWeakReference != null)
{
// 如果ShortWeakReference能引用到,那么对象是活的
Console.WriteLine(" is live", obj);
}
else if (obj.ObjectByLongWeakReference != null)
{
// 只由LongWeakReference能引用到,那么对象只存活在Finalzer线程中
Console.WriteLine(" is live in finalizer", obj);
}
else
{
// WeakRefence都引用不到,那么对象死了
Console.WriteLine(" is a dead Object");
}
}
}
}
class ObjectWithFinalizer
{
Object m_obj;
public ObjectWithFinalizer(Object obj)
{
m_obj = obj;
}
~ObjectWithFinalizer()
{
Thread.Sleep(1000);
ConsoleColor bkConsoleColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Finalized in thread {0}", Thread.CurrentThread.ManagedThreadId);
Console.ForegroundColor = bkConsoleColor;
}
}
class Program
{
public static void Main()
{
// 1. 创建 3 个对象
Program p = new Program();
Object o = new Object();
ObjectWithFinalizer owf = new ObjectWithFinalizer(o);
// 2. 创建跟踪器,并把这三个对象注册到跟踪器中
ObjectTracker objectTracker = new ObjectTracker();
objectTracker.Register(p);
objectTracker.Register(o);
objectTracker.Register(owf);
// 3. 第一次GC
GC.Collect();
objectTracker.ShowObjects();
// 输出:
// Program is a dead Object
// System.Object is live in finalizer
// ObjectWithFinalizer is live in finalizer
// 4. 等待Finalizer线程完成工作
GC.WaitForPendingFinalizers();
objectTracker.ShowObjects();
// 输出:
// Finalized in thread 2
// Program is a dead Object
// System.Object is live in finalizer
// ObjectWithFinalizer is live in finalizer
// 5. 再度GC
GC.Collect();
objectTracker.ShowObjects();
// 输出:
// Program is a dead Object
// System.Object is a dead Object
// ObjectWithFinalizer is a dead Object
}
}
值得一说的是,当Finalize线程结束工作的时候,并不会把那些Long WeakReference置null,所以仍然发现两个Object在Finalizer里面是活的,必须等到再一次GC,才能把它们收集。
那么,如果在第二次GC之前,把这个WeakReference重新赋给一个普通的对象,会发生什么事情呢?下一个例子给出了解释:
Code
using System;
class ObjectFromHell
{
public void HelloWorld()
{
Console.WriteLine("Hello World!");
}
~ObjectFromHell()
{
Console.WriteLine("Hello Hell:)");
}
}
class Program
{
public static void Main()
{
ObjectFromHell ofh = new ObjectFromHell();
WeakReference wr = new WeakReference(ofh, true);
GC.Collect();
GC.WaitForPendingFinalizers();
ObjectFromHell ofh2 = wr.Target as ObjectFromHell;
if (ofh2 != null)
ofh2.HelloWorld();
}
}
输出结果如下:
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可以使对象复生。