這期博客的話題有些沉重,我們來討論.net對象的生生死死。首先,要給生死下個定義。在這篇博客 中,每當談及一個對象是死了的對象,指的是用戶無法再獲得其引用。這個定義是個對用戶友好的定義 ,因為有很多時候,對象還殘存在托管堆上,CLR依舊可以通過一些手法來獲得它(比如RCW緩存中通過 SyncBlk),但是這種“生不如死”的狀態不在今天的討論范圍之內。
言歸正傳。眾所周知,.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則不能。舉一個例子來說。
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前後 他們是死是活。
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重新賦給一個普通的對象,會發生什麼事情呢? 下一個例子給出了解釋:
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可以使對象復生。