弱引用
当一个根指向一个对象时,该对象不可能被垃圾收集器收集,在这种情况下,通常说存在一个该对象的强引用(strong reference)。垃圾收集器还支持弱引用(weak reference)的概念。弱引用允许垃圾收集器收集对象,同时也允许应用程序访问该对象,结果取决于时间。
如果对象的弱引用存在,那么在垃圾收集执行后,该对象的内存将被执行清理。另一方面,要访问一个弱引用对象,应用程序必须获取该对象的一个强引用。如果应用程序在对象被执行之前得到了它的强引用,那么垃圾收集器将不再对该对象执行垃圾收集,因为这时存在一个该对象的强引用。
下面代码展示了弱引用的使用
class Program
{
static void Main( string [] args)
{
SomeWeakReferenceMethod();
SomeStrongtReferenceMethod();
}
static void SomeWeakReferenceMethod()
{
// 创建一个强引用对象
SomeClass o = new SomeClass();
WeakReference wr = new WeakReference(o);
o = null ; // 移除对象的强引用
o = wr.Target as SomeClass;
if (o == null )
{
// 出现过垃圾回收,对象的内存已经被回收
Console.WriteLine( " 已经被回收 " );
}
else
{
// 未出现垃圾回收
Console.WriteLine( " 没有被回收 " + o.X.ToString());
}
}
static void SomeStrongtReferenceMethod()
{
// 创建一个强引用对象
SomeClass o = new SomeClass();
o = null ; // 移除对象的强引用
try
{
Console.WriteLine(o.X);
}
catch
{
Console.WriteLine( " error " );
}
}
}
public class SomeClass
{
private int x;
public int X
{
get
{
return this .x;
}
}
public SomeClass()
{
x = 5 ;
}
}
个人感觉弱引用机制有点像缓存,将一些操作起来比较费时(比如便利系统的硬盘)的东西第一次获取之后暂时放入缓存中,并且将其引用置为null来减少应用程序的压力,但是放置在缓存中可以再次使用(减少读取时的压力),至于是否能够再次使用成功要看垃圾收集器。如果执行过垃圾收集,弱引用的对象将会被回收掉当然就无法再次使用了。
System.WeakReference有两个公用构造器:
- public WeakReference(object target);
- public WeakReference(object target, bool trackResurrection);
target表示要追踪的对象(上例中为SomeClass), trackResurrection表示是否要追踪对象的复苏,换句话说就是对象在执行Finalize方法后,是否还要追踪该对象(对象执行Finlize后对象复苏了) 。
将不追踪对象复苏的WeakReference称为短弱引用(short weak reference),而将追踪对象复苏的称为长弱引用(long weak reference)。如果一个对象没有Finalize方法,长短弱引用是一样的,最好不要使用长弱引用,因为长弱引用在一个对象被执行终止后允许该对象复苏,将会导致对象的状态不可预知。
一旦创建了对象的弱引用,通常将该对象的强引用设置为null,为了再次使用该对象,需要将弱引用转换为一个强引用,通过WeakReference的Target属性来完成的。如果target为null,那么对象已经被执行了垃圾收集,否则将会得到该对象的强引用。这时对象将不会被垃圾收集器收集了。
弱引用的内部机理
需要再次探究托管堆。托管堆中包含了两个内部数据结构来管理弱引用。即短弱引用表和长弱引用表。这两个表包含着一些指针,他们引用着托管堆对象。
当创建一个WeakReference对象时,它会在两个弱引用表中选择一个(长短弱引用相应对应),并在其中寻找一个空白插槽。该插槽的值将被设为我们希望追踪对象的地址---也就是new WeakReference构造的那个对象的地址,弱引用表将不会认为是应用程序的根,否则垃圾收集器将不能收集它们中的指针引用的对象。
垃圾收集器运行时发生的一系列事情:
- 垃圾收集器构造一个可达对象的图(.net框架读书笔记---CLR内存管理\垃圾收集(一));
- 垃圾收集器扫描短弱引用表,如果表中的对象不是前面可达对象图的一部分(对象已经不是根,在上面的例子中o已经被置为null了),那么表示该对象是一个不可达的对象,将短弱引用表中对应插槽的值将被设置为null(Target为null了);
- 垃圾收集器扫描终止化链表(.net框架读书笔记---CLR内存管理\垃圾收集(二))。如果该链表中指针引用的对象不是可达对象图的一部分,那么该对象将是不可达对象,它将被从终止化链表转移到终止化可达队列上。这时对象由成为可达对象图的一部分了。
- 垃圾收集器扫描长弱引用表,如果表中的对象不是前面可达对象图的一部分(该图现在已经包括终止化可达队列中引用的对象了),那么表示该对象是一个不可达的对象,将长弱引用表中对应插槽的值将被设置为null(Target为null了);
- 垃圾收集器压缩内存,填充不可达对象空出的位置。
继续分析代码
if (o == null )
{
// 出现过垃圾回收,对象的内存已经被回收
Console.WriteLine( " 已经被回收 " );
}
else
{
// 未出现垃圾回收
Console.WriteLine( " 没有被回收 " + o.X.ToString());
}
因为在此之前o已经被置为null了,所以它已经是一个不可达对象,如果执行垃圾收集器,o自然不在可达对象图中,那么垃圾收集器将会将短弱引用表中插槽对应的值设为null,这样Target将返回null,上面代码自然会执行if里面的代码。如果垃圾收集器还没有执行,虽然o已经为null,但是短弱引用插槽依然保存着对象的引用,那么Target将会返回对象的引用,使对象继续可达,可以使用,当然上面代码就会执行else里面的代码。
短弱引用并不追踪对象的复苏。只要垃圾收集器判断对象成为不可达的对象,它就会把短弱引用表中对应的指针设置为null。如果对象重写了Finalize方法,那么这时该方法还没有被调用,所以对象仍然存在。此时target仍然返回null,虽然这时对象已经进入终止化可达队列,对象仍然存在。