C#弱引用(WeakReference)

原文地址 :
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)之一:

  1. 對象仍處于活動狀態(tài)(對象還沒被回收报咳,也沒被標(biāo)注要進(jìn)行回收)
  2. 對象已經(jīng)被標(biāo)注要進(jìn)行回收,finalizer已排隊等待運(yùn)行挖藏,但尚未運(yùn)行暑刃。
  3. 物體已完全清理并收集。

對于長弱引用膜眠,你可以在狀態(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, 使用一下操作

  1. Ctrl+E to execute. Browse to the compiled program and open it.
  2. 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)
  3. Run command: g
  4. Run command .loadby sos clr
  5. Run command: g
  6. The program should now break at the Debugger.Break() method.
  7. 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)的:如果滿足以下所有條件,那么您可能需要考慮它:

  1. 內(nèi)存需要嚴(yán)格限制矾缓,就目前而言怀酷,很可能是移動設(shè)備。如果是在Windows RT 或者 Windows Phone那么內(nèi)存會被嚴(yán)格限制
  2. 對象的生命周期是高度可變的 - 如果你可以很好地預(yù)測對象的生命周期而账,那么使用WeakReference并沒有多大意義胰坟。 在這種情況下,你應(yīng)該直接控制對象的生命周期。
  3. 對象相對較大笔横,但易于創(chuàng)建 - WeakReference非常適合那些內(nèi)存占用很大的對象竞滓,但如果沒有,你可以根據(jù)需要輕松地重新生成它(或者不實用弱引用)吹缔。
  4. 對象的大小遠(yuǎn)遠(yuǎn)大于使用WeakReference<T>的開銷 - 使用WeakReference <T>添加了一個額外的對象商佑,這意味著更多的內(nèi)存壓力,一個額外的解除引用步驟厢塘。 使用WeakReference <T>來存儲一個比WeakReference <T>本身差不多的對象將完全浪費(fèi)時間和內(nèi)存茶没。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市晚碾,隨后出現(xiàn)的幾起案子抓半,更是在濱河造成了極大的恐慌,老刑警劉巖格嘁,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笛求,死亡現(xiàn)場離奇詭異,居然都是意外死亡糕簿,警方通過查閱死者的電腦和手機(jī)探入,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來懂诗,“玉大人蜂嗽,你說我怎么就攤上這事⊙旰悖” “怎么了植旧?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長芋类。 經(jīng)常有香客問我隆嗅,道長,這世上最難降的妖魔是什么侯繁? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任胖喳,我火速辦了婚禮,結(jié)果婚禮上贮竟,老公的妹妹穿的比我還像新娘丽焊。我一直安慰自己,他們只是感情好咕别,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布技健。 她就那樣靜靜地躺著,像睡著了一般惰拱。 火紅的嫁衣襯著肌膚如雪雌贱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天,我揣著相機(jī)與錄音欣孤,去河邊找鬼馋没。 笑死,一個胖子當(dāng)著我的面吹牛降传,可吹牛的內(nèi)容都是我干的篷朵。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼婆排,長吁一口氣:“原來是場噩夢啊……” “哼声旺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起段只,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤腮猖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后赞枕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缚够,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年鹦赎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片误堡。...
    茶點(diǎn)故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡古话,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出锁施,到底是詐尸還是另有隱情陪踩,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布悉抵,位于F島的核電站肩狂,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏姥饰。R本人自食惡果不足惜傻谁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望列粪。 院中可真熱鬧审磁,春花似錦、人聲如沸岂座。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽费什。三九已至钾恢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瘩蚪。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工泉懦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人募舟。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓祠斧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拱礁。 傳聞我的和親對象是個殘疾皇子琢锋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容