原文地址 :
Part 1: Prefer WeakReference<T> to WeakReference
Part 2: Short vs. Long Weak References and Object Resurrection
Part 3: Practical Uses
弱引用基礎(chǔ)概念
弱引用:持有對象的引用,但允許垃圾回收銷毀對象并回收內(nèi)存齿穗。
強(qiáng)引用:持有對象的引用鬓椭,防止GC回收引用對象
弱引用 的兩個版本:
第一個版本從 .NET 1.1. 就已經(jīng)存在杖虾,可以用以下代碼實例化WeakReference
var weakRef = new WeakReference(myObj);
myObj = null;
myObj 是一個已經(jīng)實例化的對象世蔗。 一旦將其分配給weakRef變量细疚,就應(yīng)該將原始強(qiáng)引用設(shè)置為null饮寞。 現(xiàn)在张惹,只要有垃圾回收息裸,就可以回收weakRef所指的對象蝇更。
你可能想通過WeakReference的IsAlive屬性,判斷是否可以訪問弱引用的對象呼盆,如下例所示:
WeakReference ref1 = new WeakReference(new MyObject());
if (ref1.IsAlive)
{
// wrong!!!!!
DoSomething(ref1.Target as MyObject);
}
IsAlive 這個屬性并沒有什么軟用年扩,當(dāng)值為false時,表示對象被回收了访圃,不會有什么問題厨幻。但是當(dāng)值為true時,有可能在對象添加強(qiáng)引用的一瞬前腿时,對象就被垃圾回收機(jī)制回收掉况脆。
正確的使用弱引用指定的對象,如下所示:
MyObject obj = ref1.Target as MyObject;
if (obj != null)
{
// correct
DoSomething(obj);
}
你可能會問批糟,既然可以通過WeakReference<T>
實例化一個弱引用對象格了,為什么還會存在SetTarget
方法呢?
短弱引用 vs 長弱引用
在CLR(.NET運(yùn)行時)中存在兩種弱引用:
- Short Weak Reference(短弱引用):一旦對象被垃圾回收跃赚,引用就會設(shè)置為null笆搓,上文所舉的例子都是短弱引用。
- Long Weak Reference(長弱引用):如果對象存在一個finalizer(終結(jié)器/析構(gòu)函數(shù)) 并且引用被以正確的方式創(chuàng)建纬傲。那么引用將一直指向?qū)ο舐埽钡絝inalizer結(jié)束。
短弱引用相對比較好理解叹括,一旦發(fā)生垃圾回收算墨,對象被回收銷毀。引用就被設(shè)置為空汁雷。一個短弱引用只有兩種狀態(tài):alive(存活) 或者 collected(被回收)
長弱引用相對復(fù)雜些净嘀,因為引用的對象可以處于以下三種狀態(tài)之一:
- 對象仍處于活動狀態(tài)(對象還沒被回收报咳,也沒被標(biāo)注要進(jìn)行回收)
- 對象已經(jīng)被標(biāo)注要進(jìn)行回收,finalizer已排隊等待運(yùn)行挖藏,但尚未運(yùn)行暑刃。
- 物體已完全清理并收集。
對于長弱引用膜眠,你可以在狀態(tài)1和狀態(tài)2中獲取到對象的引用岩臣。狀態(tài)1與短弱引用相同,但狀態(tài)2很棘手宵膨。 該狀態(tài)下對象處可能未定義架谎。 垃圾收集已經(jīng)開始,一旦終結(jié)器線程開始運(yùn)行掛起的finalizer辟躏,該對象將被清除谷扣。 這可能在任何時候發(fā)生,因此使用該對象非常棘手捎琐。 在目標(biāo)對象的finalizer完成之前会涎,對目標(biāo)對象的弱引用保持非null。
可以使用以下構(gòu)造函數(shù)野哭,創(chuàng)建長弱引用:
WeakReference<MyObject> myRefWeakLong
= new WeakReference<MyObject>(new MyObject(), true);
參數(shù)true表示要 track resurrection(跟蹤恢復(fù))在塔,這是新術(shù)語,也是長弱引用的重點(diǎn)拨黔。
題外話:Resurrection(復(fù)活)
首先蛔溃,強(qiáng)調(diào)下:不要這樣做。 你不需要它篱蝇。 不要試試贺待。 你會明白為什么。 我不知道在.NET中是否允許Resurrection(復(fù)活)是有特殊原因的零截,或者它只是垃圾回收工作的自然結(jié)果麸塞,但是沒有充分的理由去做這樣的事情。
所以這是不該做的:
class Program
{
class MyObject
{
~MyObject()
{
myObj = this;
}
}
static MyObject myObj = new MyObject();
static void Main(string[] args)
{
myObj = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
通過將myObj的引用設(shè)置給一個對象涧衙,而復(fù)活myObj哪工。這樣處理有非常多壞處:
- 只能復(fù)活一個對象一次,因為該對象已經(jīng)被垃圾回收機(jī)制標(biāo)記為gen 1弧哎。所以這個對象的生命周期有限制雁比。
- 除非你在這個對象上調(diào)用GC.ReRegisterForFinalize() ,否則finalizer(終結(jié)器)不會再次運(yùn)行。
- 對象處于不可確定狀態(tài)撤嫩,對象加載的本地資源已經(jīng)被釋放偎捎,需要被重新實例化。單獨(dú)處理這些相對麻煩。
- 復(fù)活對象引用的對象也會被復(fù)活茴她。如果這些對象存在finalizers(終結(jié)器)寻拂,這些終結(jié)器也有可能已經(jīng)被執(zhí)行。這些對象的狀態(tài)也不好確認(rèn)丈牢。
那為什么這種情況還會發(fā)生祭钉?有些語言認(rèn)為這是個bug,你也會這么認(rèn)為赡麦。有些人把Resurrection(復(fù)活)這技術(shù)用于對象池朴皆,但這是比較復(fù)雜的做法,而且存在很多更好的方式去實現(xiàn)泛粹。你應(yīng)該將對象復(fù)活視為一個bug。
弱引用 vs 強(qiáng)引用 vs 終結(jié)器行為
WeakReference<T>可以用兩個維度來標(biāo)識: 弱引用初始化時的參數(shù)肮疗,以及該弱引用是否有finalizer晶姊。
No finalizer | Has finalizer | |
---|---|---|
trackResurrection = false | short | short |
trackResurrection = true | short | long |
一個有趣的案例沒有在文檔中明確指出,當(dāng)trackResurrection為false時伪货,該對象確實有一個終結(jié)器们衙。弱引用什么時候被設(shè)置為null?它遵循short weak references(短弱引用)的規(guī)則碱呼,垃圾回收時設(shè)置為null蒙挑。那么,垃圾回收時對象會被標(biāo)識為gen 1愚臀,終結(jié)器也被添加到執(zhí)行隊列忆蚀。這時候終結(jié)器仍然可以復(fù)活對象。不過重點(diǎn)是姑裂,弱引用trackResurrection為false(沒有監(jiān)聽復(fù)活)馋袜。弱引用的參數(shù)不會影響到垃圾回收機(jī)制,只是影響到弱引用本身舶斧。
你可以使用以下代碼在實踐中看到這一點(diǎn):
class MyObjectWithFinalizer
{
~MyObjectWithFinalizer()
{
var target = myRefLong.Target as MyObjectWithFinalizer;
Console.WriteLine("In finalizer. target == {0}",
target == null ? "null" : "non-null");
Console.WriteLine("~MyObjectWithFinalizer");
}
}
static WeakReference myRefLong =
new WeakReference(new MyObjectWithFinalizer(), true);
static void Main(string[] args)
{
GC.Collect();
MyObjectWithFinalizer myObj2 = myRefLong.Target
as MyObjectWithFinalizer;
Console.WriteLine("myObj2 == {0}",
myObj2 == null ? "null" : "non-null");
GC.Collect();
GC.WaitForPendingFinalizers();
myObj2 = myRefLong.Target as MyObjectWithFinalizer;
Console.WriteLine("myObj2 == {0}",
myObj2 == null ? "null" : "non-null");
}
輸出結(jié)果如下
myObj2 == non-null
In finalizer. target == non-null
~MyObjectWithFinalizer
myObj2 == null
調(diào)試器中查找弱引用
Windbg可以向您展示如何找到您的弱引用的位置欣鳖,包括短弱引用和長弱引用。
以下是一些示例代碼:
using System;
using System.Diagnostics;
namespace WeakReferenceTest
{
class Program
{
class MyObject
{
~MyObject()
{
}
}
static void Main(string[] args)
{
var strongRef = new MyObject();
WeakReference<MyObject> weakRef =
new WeakReference<MyObject>(strongRef, trackResurrection: false);
strongRef = null;
Debugger.Break();
GC.Collect();
MyObject retrievedRef;
// Following exists to prevent the weak references themselves
// from being collected before the debugger breaks
if (weakRef.TryGetTarget(out retrievedRef))
{
Console.WriteLine(retrievedRef);
}
}
}
}
Release 模式編譯代碼
In Windbg, 使用一下操作
- Ctrl+E to execute. Browse to the compiled program and open it.
- Run command: sxe ld clrjit (this tells the debugger to break when the clrjit.dll file is loaded, which you need before you can execute .loadby)
- Run command: g
- Run command .loadby sos clr
- Run command: g
- The program should now break at the Debugger.Break() method.
- Run command !gchandles
你可以得到類似下面的輸出:
0:000> !gchandles
Handle Type Object Size Data Type
011112f4 WeakShort 02d324b4 12 WeakReferenceTest.Program+MyObject
011111d4 Strong 02d31d70 36 System.Security.PermissionSet
011111d8 Strong 02d31238 28 System.SharedStatics
011111dc Strong 02d311c8 84 System.Threading.ThreadAbortException
011111e0 Strong 02d31174 84 System.Threading.ThreadAbortException
011111e4 Strong 02d31120 84 System.ExecutionEngineException
011111e8 Strong 02d310cc 84 System.StackOverflowException
011111ec Strong 02d31078 84 System.OutOfMemoryException
011111f0 Strong 02d31024 84 System.Exception
011111fc Strong 02d3142c 112 System.AppDomain
011113ec Pinned 03d333a8 8176 System.Object[]
011113f0 Pinned 03d32398 4096 System.Object[]
011113f4 Pinned 03d32178 528 System.Object[]
011113f8 Pinned 02d3121c 12 System.Object
011113fc Pinned 03d31020 4424 System.Object[]
Statistics:
MT Count TotalSize Class Name
70e72554 1 12 System.Object
01143814 1 12 WeakReferenceTest.Program+MyObject
70e725a8 1 28 System.SharedStatics
70e72f0c 1 36 System.Security.PermissionSet
70e724d8 1 84 System.ExecutionEngineException
70e72494 1 84 System.StackOverflowException
70e72450 1 84 System.OutOfMemoryException
70e722fc 1 84 System.Exception
70e72624 1 112 System.AppDomain
70e7251c 2 168 System.Threading.ThreadAbortException
70e35738 4 17224 System.Object[]
Total 15 objects
Handles:
Strong Handles: 9
Pinned Handles: 5
Weak Short Handles: 1
短弱引用被標(biāo)識為“Weak Short Handle”
WeakReference的實際用途
何時使用WeakReference
簡單的說:很少茴厉。 大多數(shù)應(yīng)用程序不需要這個泽台。
長一點(diǎn)的:如果滿足以下所有條件,那么您可能需要考慮它:
- 內(nèi)存需要嚴(yán)格限制矾缓,就目前而言怀酷,很可能是移動設(shè)備。如果是在Windows RT 或者 Windows Phone那么內(nèi)存會被嚴(yán)格限制
- 對象的生命周期是高度可變的 - 如果你可以很好地預(yù)測對象的生命周期而账,那么使用WeakReference并沒有多大意義胰坟。 在這種情況下,你應(yīng)該直接控制對象的生命周期。
- 對象相對較大笔横,但易于創(chuàng)建 - WeakReference非常適合那些內(nèi)存占用很大的對象竞滓,但如果沒有,你可以根據(jù)需要輕松地重新生成它(或者不實用弱引用)吹缔。
- 對象的大小遠(yuǎn)遠(yuǎn)大于使用WeakReference<T>的開銷 - 使用WeakReference <T>添加了一個額外的對象商佑,這意味著更多的內(nèi)存壓力,一個額外的解除引用步驟厢塘。 使用WeakReference <T>來存儲一個比WeakReference <T>本身差不多的對象將完全浪費(fèi)時間和內(nèi)存茶没。