弱引用 WeakReference

在程序设计中我们经常会进行一些全局缓存设计,诸如使用静态或者全局根字段来引用某个对象,以便一次创建多次使用。

如:
    class BigData
    {
    }

    class Program
    {
        static BigData cache;

        public static BigData DataCache
        {
            get 
            {
                if (cache== null) cache= new BigData();
                return cache;
            }
        }
    }

但是这样做在某些时候会存在一些弊端,如:
1. 当dataCache并没有被频繁使用,甚至因为某些原因仅仅被使用了一次时会造成内存资源的浪费。
2. 由于GC只能回收不可达对象,因此即便内存不足,GC也无法回收这些闲置资源。

这时建议你使用 WeakReference 来重构你的程序,以便获得更好的系统性能。
WeakReference :“弱引用”,即在引用对象的同时仍然允许对该对象进行垃圾回收。
使用弱引用后,不应该再使用强引用,有关细节可以参考SDK帮助文档。
    class BigData
    {
        ~BigData()
        {
            Console.WriteLine("Destory...");
        }

        public void Test()
        {
            Console.WriteLine("Test");
        }
    }

    class Program
    {
        static WeakReference cache = new WeakReference(null);

        public static BigData DataCache
        {
            get 
            {
                BigData data = cache.Target as BigData;
                if (data == null)
                {
                    data = new BigData();
                    cache.Target = data;
                }

                return data;
            }
        }

        static void Main(string[] args) 
        { 
            DataCache.Test();
            DataCache.Test();

            GC.Collect();
            DataCache.Test();
        } 
    }

改进后的程序,我们依旧可以实现我们缓存的目的,而GC也可以在合时的时候释放cache占用的内存。
.NET中的缓存功能多采用了类似的设计。
当然并非要求所有的场合都适合使用弱引用。

补充:

弱引用分为"短弱引用(Short Week Reference)"和"长弱引用(Long Week Reference)",其区别是长弱引用在对象的Finalize方法被GC调用后依然追踪对象。基于安全考虑,不推荐使用长弱引用。

因此建议使用
WeakReference wr = new WeakReference(object);
WeakReference wr = new WeakReference(object, false);
来自:http://www.rainsts.net/feed.asp?q=comment&id=78

======================================

[1]强引用 如果有强引用存在,GC是不会回收对象的。 [2]弱引用 弱引用可以让您保持对对象的引用, 同时允许GC在必要时释放对象,回收内存。 [3]弱引用使用场景 对于那些创建便宜但耗费大量内存的对象, 希望保持该对象,又要在应用程序需要时使用, 同时希望GC必要时回收时,可以考虑使用弱引用。

在了解使用WeakReference之前.我想大家先了解一下CLR无用内存回收机制. 1.NET中的托管对象. 1>普通对象:即没有使用系统资源的对象,故没胡析构函数. 2>使用系统资源的对象:带析构函数并在其中释放系统资源.(文件,各种流,网络连接,数据库连接等(内存也是系统资源,但这里的系统资源并不包括内存)). 2.使用系统资源的五大步骤: 1>为代表资源的类型分配内存. 2>初始化资源的状态.请求非内存的系统资源(打开文件,建立数据库连接等) 3>通过访问类型的实例及其成员来访问资源。 4>清空资源状态,释放系统资源。(关闭文件,关闭数据库连接等)。 5>释放内存。 对于普通对象,只有上面的.1,3,5步,而对于使用了系统资源的对象才有上面的全部5步。 CLR默认调用Finalize()来实现第4>步。使用了系统资源的类应该实现Dispose()方法,通过程序员主动调用来释放使用的系统资源,避免CLR调用Finalize()方法带来的性能损失。因为无用内存回收(Garbage Collection 简称GC)运行时,会中止当前程序的执行,大大影响程序的性能。 3.引用类型对象在内存中的分配 Class Test{ private int x,y; public Test(int x,int y){ this.x = x; this.y = y; } } Test t1 = new Test(0,0); Test t2 = new Test(1,1); Test t3 = new Test(2,4); 当new Test(0,0)所执行的动作如下: 1>托管堆manage heap 中分配指定类型所需字节空间作为存储其对象内存空间并将该空间清0; 2>初始化对象的2个附加成员:指向类型方法表的指针和syncBlockIndex指针。 3>引起对构造函数的调用。 4>返回指向新建对象的引用。(把一个对象引用分配给一个变量,该变量就包含对对象的一个强引用。GC不会收回强引用任在使用的对象。只有当变量离开作用域时,或是显式的给变量分配VB.NET(Nothing) or C#.NET (NULL)时,强引用才被删除。) ---Stack------ ----------------Manage Heap--------------------- t3 objact t3 t2 objact t2 t1 objact t1 4.CLR GC内存分配 当对象过多,manage heap分配光了或者不足分配新的对象时,CLR启动 GC,这时主线程被停止运行。GC将堆中不再被使用的对象占用的内存释放掉,然后整理堆,使剩余空间连续。如果没有可以释放的对象,或者释放了无用对象后内存还是不足,则CLR会抛出一个OutOfMemoryException异常。 5.GC如何判断对象不再使用 每一个应用都有一组根,这些根包括了标示托管堆中一组对象的存储单元。被认为是根的对象包括: 1.所有的全局和静态对象; 2.一个线成栈中的局部对象和函数的参数; 3.任何CPU寄存器包含的指针指向的对象; 上面根的列表由JIT和CLR维护并且GC可以访问。 开始无用单元回收后GC就开始遍历根,找到第一个根指向的对象,沿着这个对象向下找,找到这个对象所有引用的对象,以及引用之引用,并将其放入到一个集合中。这一个完成后,就接着找下一个根。一旦GC 发现某个对象已经在集合中,就停止这个分支的搜寻以防止重复和引用死循环。 完成后,GC就有了一个所有根可以访问到的对象的集合,这个集合中没有的对象就认为是无用的。如下:

根:可简单理解为有引用指向他。 简单来说,就是有引用指向他的对象是有用的,而没有引用指向的对象则认为不再使用。 Finalize 分配使用资源一共五步,我们已经知道了GC是如何释放无用对象的内存了。但是它怎么实现第四步清空资源使用状态、释放利用到的一些非内存的系统资源呢?.NET引入了Finalize来完成这个任务。 引用了系统资源才需要override Finalize()方法。 GC在无用单元回收时一旦发现某个对象有Finalize方法,便调用它。所以我们的Finalize方法一定要尽量少做事情,以提高内存回收的速度。另外,在Finalize方法中不要有任何的线程同步等操作,以防止GC线程被挂起。 我们可以用两种方法来写自己的Finalize方法。一种就是显示的实现,如下面的代码: Code1:
public class SomeClass
{
public SomeClass()
{
}
protected override void Finalize()
{
Console.WriteLine(“Finalizing…”);
}
} 使用这种方法时要注意一点,.NET不会帮你做调用基类的Finalize方法。如果需要调用基类的Finalize方法,需要显示的调用。如下面代码: Code2 public class SomeClass { public SomeClass() { } protected override void Finalize() { Console.WriteLine(“Finalizing…”); base.Finalize(); // 调用基类Finalize方法 } } 另外一种方法就是析构函数。C#中的析构函数不同于C++。我们看下面的代码:
public class SomeClass
{
public SomeClass()
{
}
~SomeClass()
{
Console.WriteLine(“Finalizing…”);
}
} 它等同于Code2。 使用Finalize方法要特别小心。因为使用Finalize方法的对象要比普通的对象花时间;GC也要花更多的时间来回收。而且CLR并不能保证调用 Finalize的顺序,所以如果对象间有关联(比如一个成员变量先被Finalize了,如果在Finalize方法里还使用它,就会出错),就会更麻烦。 GC是如何实现Finalize的呢?GC维护了两个队列,Finalization队列和Freachable队列。在托管堆分配对象的时候,GC如果发现这个对象实现了一个Finalize方法,就把它加到Finalization队列。 当托管堆的内存不足的时候,GC开始对堆进行回收。GC回收一个对象前,先检查Finalization队列中是否有这个对象的指针,如果有,就将其放入 Freachable队列。Freachable队列被认为是根(root)的一部分,所以GC不会对其作回收。GC第一次回收后, 对象G和对象E不在根的范围之内,被回收。对象F和对象C由于需要Finalize被放入到Freachable队列,这个队列被认为是根的一部分,所以这是对象F和对象C就复活了,没有被GC回收。Freachable队列中的对象的Finalize方法被一个特殊的线程执行。这个线程平时处于非活动状态,一旦Freachable队列不再为空,它就醒过来,一一执行这个队列中对象中的Finalize方法。执行过后如下图: 这时对象F和对象C不再是根的一部分,如果此时GC进行回收,将会被认作无用对象进行回收, 上面简单描述了Finalize作用及其内部的工作原理。下面来说一下Generation。 Generation 每次都对整个对进行搜索,压缩是非常耗时的。微软总结了一些过去的开发中出现的现象,其中有一条就是,越是新的对象,越是最快被丢弃不再使用。微软根据这个经验在内存回收中引入了Generation的概念,我此处暂时将其翻译成代。托管堆开始的时候是空的,程序启动开始在其中分配对象,这时候的对象就是第 0代(Generation 0)对象。 接下来,到托管堆空间不足,GC进行了第一次回收,剩下的没有被回收的对象就升为第一代,之后再新分配的对象就是第0代(图甲)。再之后GC再进行回收的话只回收第0代,未被回收的第0代升级为第一代,原来的第一代升级为第0代(图乙)。 GC缺省的代(Generation)最高就是2,升级到第二代就不会再升级了。那什么时候GC回收第一,第二代呢?当GC回收完第0代后,发现内存空间还不够,就会回收第一代,回收完第一代,还不够,就回收第二代。 总结:普通无用对象, GC第1次运行就回收,使用了系统资源的无用对象(有析构函数或者override Finalize()的对象)GC要2才能回收。 WeakReference(弱引用) 我们平常用的都是对象的强引用,如果有强引用存在,GC是不会回收对象的。我们能不能同时保持对对象的引用,而又可以让GC需要的时候回收这个对象呢?.NET中提供了WeakReference来实现。 弱引用可以让您保持对对象的引用,同时允许GC在必要时释放对象,回收内存。对于那些创建便宜但耗费大量内存的对象,即希望保持该对象,又要在应用程序需要时使用,同时希望GC必要时回收时,可以考虑使用弱引用。弱引用使用起来很简单,看下面的代码: Object obj = new Object(); WeakReference wref = new WeakReference( obj ); obj = null; 第一行代码新建了一个新的对象,这里叫它对象A,obj是对对象A的强引用。接着第二行代码新建了一个弱引用对象,参数就是对象A的强引用,第三行代码释放掉对对象A的强引用。这时如果GC进行回收,对象A就会被回收。 怎样在取得对象A的强引用呢?很简单,请看代码2: Object obj2 = wref.Target; if( obj2 != null ) { // 做你想做的事吧。 } else { // 对象已经被回收,如果要用必须新建一个。 } 只要显示的将弱引用的Target属性附值就会得到弱引用所代表对象的一个强引用。不过在使用对象之前要对其可用性进行检查,因为它可能已经被回收了。如果你得到的是null(VB.NET下为Nothing),表明对象已经被回收,不能再用了,需要重新分配一个。如果不是null,就可以放心大胆的用了。 接下来让我们看WeakReference的另外一个版本,请看代码3: // public WeakReference( // object target, // bool trackResurrection //); Object obj1 = new Object(); Object obj2 = new Object(); WeakReference wref1 = new WeakReference( obj1, false ); WeakReference wref2 = new WeakReference( obj2, true ); WeakReference的另外一个版本有两个参数,第一个参数和我们前面用的版本的一样。第二个参数让我们看一下他的原型,bool trackResurrection,跟踪复活,是个bool型,就是是否跟踪复活。前面的文章中我提到过需要Finalize的对象在最终释放前会有一次复活,我们大概可以猜到第二个参数表示的意思了。如果我们第二个参数给false,这个弱引用就是一个short weak reference(短弱引用),当GC回收时,发现根中没有这个对象的引用了,就认为这个对象无用,这时短弱引用对这个对象的跟踪到此为止,弱引用的 Target被设置为null。前面的一个参数的构造函数版本新建的弱引用为短弱引用。如果第二个参数给true,这个弱引用就是一个long weak reference(长弱引用)。在对象的Finalize方法没有被执行以前,Target都可用。不过这是对象的某些成员变量也许已经被回收,所以使用起来要想当小心。 现在让我们看看WeakReference是如何实现的。很显然WeakReference不能直接的引用目标对象,WeakReference的Target属性的get/set是两个函数,从某处查到目标对象的引用返回,而不是我们最常用写的那样直接返回或者设置一个私有变量。GC维护了两个列表来跟踪两种弱引用的目标对象,在一个 WeakReference对象创建时,它在相应的列表中找到一个位置,将目标对象的引用放入,很显然,这两个列表不是根的一部分。在GC进行内存回收的时候,如果要回收某一个对象,会检查弱引用的列表,如果保存着这个对象的引用,则将其设为null。
public class AspPage : Page { private static ArrayList __ENCList = new ArrayList(); [DebuggerNonUserCode] public AspPage() { base.Load += new EventHandler(this.Page_Load); ArrayList list = __ENCList; lock (list) { __ENCList.Add(new WeakReference(this)); } } }
来自:http://hi.baidu.com/czh0221/blog/item/57368f827716d9b76d81195b.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值