这篇文章接着上一次的来,继续讨论无用资源回收的其它一些话题。
l WeakReference (弱引用)
我们平常用的都是对象的强引用,如果有强引用存在, GC 是不会回收对象的。我们能不能同时保持对对象的引用,而又可以让 GC 需要的时候回收这个对象呢? .NET 中提供了 WeakReference 来实现。弱引用使用起来很简单,看下面的代码:
代码 1
Object obj = new Object();
WeakReference wref = new WeakReference( obj );
obj = null;
第一行代码新建了一个新的对象,这里叫它对象 A , obj 是对对象 A 的强引用。接着第二行代码新建了一个弱引用对象,参数就是对象 A 的强引用,第三行代码释放掉对对象 A 的强引用。这时如果 GC 进行回收,对象 A 就会被回收。
怎样在取得对象 A 的强引用呢?很简单,请看代码 2 :
代码 2
Object obj2 = wref.Target;
if( obj2 != null )
{
… // 做你想做的事吧。
}
else
{
…// 对象已经被回收,如果要用必须新建一个。
}
只要显示的将弱引用的 Target 属性附值就会得到弱引用所代表对象的一个强引用。不过在使用对象之前要对其可用性进行检查,因为它可能已经被回收了。如果你得到的是 null ( VB.NET 下为 Nothing ),表明对象已经被回收,不能再用了,需要重新分配一个。如果不是 null ,就可以放心大胆的用了。
接下来让我们看 WeakReference 的另外一个版本,请看代码 3 :
代码 3
// **pub** **li** **c 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 型,就是是否跟踪复活。前面的文章中我提到过需要 Fina li ze 的对象在最终释放前会有一次复活,我们大概可以猜到第二个参数表示的意思了。如果我们第二个参数给 false ,这个弱引用就是一个 short weak reference (短弱引用),当 GC 回收时,发现根中没有这个对象的引用了,就认为这个对象无用,这时短弱引用对这个对象的跟踪到此为止,弱引用的 Target 被设置为 null 。前面的一个参数的构造函数版本新建的弱引用为短弱引用。如果第二个参数给 true ,这个弱引用就是一个 long weak reference (长弱引用)。在对象的 Fina li ze 方法没有被执行以前, Target 都可用。不过这是对象的某些成员变量也许已经被回收,所以使用起来要想当小心。
现在让我们看看 WeakReference 是如何实现的。很显然 WeakReference 不能直接的引用目标对象, WeakReference 的 Target 属性的 get/set 是两个函数,从某处查到目标对象的引用返回,而不是我们最常用写的那样直接返回或者设置一个私有变量。 GC 维护了两个列表来跟踪两种弱引用的目标对象,在一个 WeakReference 对象创建时,它在相应的列表中找到一个位置,将目标对象的引用放入,很显然,这两个列表不是根的一部分。在 GC 进行内存回收的时候,如果要回收某一个对象,会检查弱引用的列表,如果保存着这个对象的引用,则将其设为 null 。
l ** 控制 GC ** ** 行为 **
.NET 提供了 System.GC 类来控制 GC 的行为, GC 只提供静态方法,无需也不能( GC 的构造方法被做成私有)创建它的实例。
GC 类提供的最主要一个方法就是 Collect ,它使自己控制内存回收成为可能。 Collect 方法有两种版本, void Collect (); 和 void Collect (int); 。第二个版本的 Collect 提供一个参数,让你选择是回收那一代( Generation )以及比其年轻者的对象,也就是说 GC.Collect(0) 只回收第 0 代的对象,而 GC.Collect(1) 则是要回收第 0 代和第一代的对象。 Collect() 则是回收所有对象,等同于 GC.Collection(GC.MaxGeneration) 。 MaxGeneration 是 GC 唯一的一个属性,它给出 GC 的最高代。
GC 类提供了另外一个方法来获取某个对象的代值, GetGeneration 。代码 4 给出了一段例子代码,可以让我们更好的理解 Generation 和 GC 提供的这两个方法。请看代码 4 :
代码 4
class GCDemoClass
{
~GCDemoClass()
{
Console.WriteLine( "Demo Class Fina li zing..." );
}
}
static void Main ( string [] args)
{
GCDemoClass inst = new GCDemoClass();
Console.WriteLine( "Generation of demo object:{0} ", GC.GetGeneration( inst ) );
GC.Collect();
Console.WriteLine( "Generation of demo object:{0} ", GC.GetGeneration( inst ) );
GC.Collect();
Console.WriteLine( "Generation of demo object:{0} ", GC.GetGeneration( inst ) );
inst = null ;
GC.Collect( 0 );
Console.WriteLine( " After collect generation 0 ..." );
GC.Collect( 1 );
Console.WriteLine( " After collect generation 1 ... " );
GC.Collect( 2 );
Console.WriteLine( " After collect generation 2 ... " );
Console.ReadLine();
}
GCDemoClass 实现了一个析构函数,根据我前面文章提到的,编译器会将其变为 Fina li ze 方法,在正式回收这个类的实例的内存之前调用。我们把 new GCDemoClass( ) 新建的对象叫做对象 A ,这时它是第 0 代对象,由于 inst 保存着一个它的强引用,所以前两次的 Collect 不会将对象 A 回收,而随着 Collect ,对象 A 的 Generation 也随之增加,第二次 Collect 后,对象 A 成为第二代对象。接下来, inst = null 放弃了对对象 A 的强引用,但是由于为第二代对象所以 Collect(0) 和 Collect(1) 都没有将其回收。终于, Collect(2) 被执行,对象 A 别回收,同时其 Fina li ze 方法也被调用。这段代码执行的结果如下:
GC 还提供了其他的一些方法,这里就不再讨论了,大家可以去看 MSDN 。