C#弱引用(WeakReference)

原文地址 :
Part 1: Prefer WeakReference<T> to WeakReference
Part 2: Short vs. Long Weak References and Object Resurrection
Part 3: Practical Uses

弱引用基础概念

弱引用:持有对象的引用,但允许垃圾回收销毁对象并回收内存。
强引用:持有对象的引用,防止GC回收引用对象

弱引用 的两个版本:

第一个版本从 .NET 1.1. 就已经存在,可以用以下代码实例化WeakReference

var weakRef = new WeakReference(myObj);
myObj = null;

myObj 是一个已经实例化的对象。 一旦将其分配给weakRef变量,就应该将原始强引用设置为null。 现在,只要有垃圾回收,就可以回收weakRef所指的对象。

你可能想通过WeakReference的IsAlive属性,判断是否可以访问弱引用的对象,如下例所示:

WeakReference ref1 = new WeakReference(new MyObject());
if (ref1.IsAlive)
{
    // wrong!!!!!
    DoSomething(ref1.Target as MyObject);
}

IsAlive 这个属性并没有什么软用,当值为false时,表示对象被回收了,不会有什么问题。但是当值为true时,有可能在对象添加强引用的一瞬前,对象就被垃圾回收机制回收掉。
正确的使用弱引用指定的对象,如下所示:

MyObject obj = ref1.Target as MyObject;
if (obj != null)
{
    // correct
    DoSomething(obj);
}

你可能会问,既然可以通过WeakReference<T>实例化一个弱引用对象,为什么还会存在SetTarget方法呢?

短弱引用 vs 长弱引用

在CLR(.NET运行时)中存在两种弱引用:

  • Short Weak Reference(短弱引用):一旦对象被垃圾回收,引用就会设置为null,上文所举的例子都是短弱引用。
  • Long Weak Reference(长弱引用):如果对象存在一个finalizer(终结器/析构函数) 并且引用被以正确的方式创建。那么引用将一直指向对象,直到finalizer结束。

短弱引用相对比较好理解,一旦发生垃圾回收,对象被回收销毁。引用就被设置为空。一个短弱引用只有两种状态:alive(存活) 或者 collected(被回收)

长弱引用相对复杂些,因为引用的对象可以处于以下三种状态之一:

  1. 对象仍处于活动状态(对象还没被回收,也没被标注要进行回收)
  2. 对象已经被标注要进行回收,finalizer已排队等待运行,但尚未运行。
  3. 物体已完全清理并收集。

对于长弱引用,你可以在状态1和状态2中获取到对象的引用。状态1与短弱引用相同,但状态2很棘手。 该状态下对象处可能未定义。 垃圾收集已经开始,一旦终结器线程开始运行挂起的finalizer,该对象将被清除。 这可能在任何时候发生,因此使用该对象非常棘手。 在目标对象的finalizer完成之前,对目标对象的弱引用保持非null。

可以使用以下构造函数,创建长弱引用:

WeakReference<MyObject> myRefWeakLong 
    = new WeakReference<MyObject>(new MyObject(), true);

参数true表示要 track resurrection(跟踪恢复),这是新术语,也是长弱引用的重点。

题外话:Resurrection(复活)

首先,强调下:不要这样做。 你不需要它。 不要试试。 你会明白为什么。 我不知道在.NET中是否允许Resurrection(复活)是有特殊原因的,或者它只是垃圾回收工作的自然结果,但是没有充分的理由去做这样的事情。

所以这是不该做的:

class Program
{
    class MyObject
    {
        ~MyObject()
        {
            myObj = this;
        }
    }

    static MyObject myObj = new MyObject();

    static void Main(string[] args)
    {
        myObj = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

通过将myObj的引用设置给一个对象,而复活myObj。这样处理有非常多坏处:

  • 只能复活一个对象一次,因为该对象已经被垃圾回收机制标记为gen 1。所以这个对象的生命周期有限制。
  • 除非你在这个对象上调用GC.ReRegisterForFinalize() ,否则finalizer(终结器)不会再次运行。
  • 对象处于不可确定状态,对象加载的本地资源已经被释放,需要被重新实例化。单独处理这些相对麻烦。
  • 复活对象引用的对象也会被复活。如果这些对象存在finalizers(终结器),这些终结器也有可能已经被执行。这些对象的状态也不好确认。

那为什么这种情况还会发生?有些语言认为这是个bug,你也会这么认为。有些人把Resurrection(复活)这技术用于对象池,但这是比较复杂的做法,而且存在很多更好的方式去实现。你应该将对象复活视为一个bug。

弱引用 vs 强引用 vs 终结器行为

WeakReference<T>可以用两个维度来标识: 弱引用初始化时的参数,以及该弱引用是否有finalizer。

No finalizer Has finalizer
trackResurrection = false short short
trackResurrection = true short long

一个有趣的案例没有在文档中明确指出,当trackResurrection为false时,该对象确实有一个终结器。弱引用什么时候被设置为null?它遵循short weak references(短弱引用)的规则,垃圾回收时设置为null。那么,垃圾回收时对象会被标识为gen 1,终结器也被添加到执行队列。这时候终结器仍然可以复活对象。不过重点是,弱引用trackResurrection为false(没有监听复活)。弱引用的参数不会影响到垃圾回收机制,只是影响到弱引用本身。
你可以使用以下代码在实践中看到这一点:

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"); 
}

输出结果如下

myObj2 == non-null 
In finalizer. target == non-null 
~MyObjectWithFinalizer 
myObj2 == null 
调试器中查找弱引用

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

短弱引用被标识为“Weak Short Handle”

WeakReference的实际用途

何时使用WeakReference

简单的说:很少。 大多数应用程序不需要这个。
长一点的:如果满足以下所有条件,那么您可能需要考虑它:

  1. 内存需要严格限制,就目前而言,很可能是移动设备。如果是在Windows RT 或者 Windows Phone那么内存会被严格限制
  2. 对象的生命周期是高度可变的 - 如果你可以很好地预测对象的生命周期,那么使用WeakReference并没有多大意义。 在这种情况下,你应该直接控制对象的生命周期。
  3. 对象相对较大,但易于创建 - WeakReference非常适合那些内存占用很大的对象,但如果没有,你可以根据需要轻松地重新生成它(或者不实用弱引用)。
  4. 对象的大小远远大于使用WeakReference<T>的开销 - 使用WeakReference <T>添加了一个额外的对象,这意味着更多的内存压力,一个额外的解除引用步骤。 使用WeakReference <T>来存储一个比WeakReference <T>本身差不多的对象将完全浪费时间和内存。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348

推荐阅读更多精彩内容