简单说说.Net中的弱引用

弱引用是什么?

要搞清楚什么是弱引用,咱们须要先知道强引用是什么。强引用并非什么深奥的概念,其实咱们平时所使用的.Net引用就是强引用。例如:缓存

Cat kitty = new Cat();

变量kitty就是一个强引用,它指向了堆中的一个Cat对象实例。咱们都知道,CLR的垃圾回收机制会标记全部被强引用到的对象,而那些剩下的未被标记的对象则会被垃圾回收。换句话说,若是一个对象一直被某个强引用所指向,那么它是不会被垃圾回收的。函数

从这一点来看,弱引用就彻底不同了——即便某个对象被弱引用所指向,该对象仍然会被垃圾回收。也就是说,弱引用不会影响对象的生命周期。工具

System.WeakReference类是.net为咱们提供的一个弱引用的实现,能够这么用:性能

WeakReference weakReference = new WeakReference(new Cat());
Cat strongReference = weakReference.Target as Cat;
if (strongReference != null)
{
    // Cat对象实例还没有被垃圾回收,能够经过strongReference进行访问
}
else
{
    // Cat对象实例已被垃圾回收
}

若是在上例的第一行代码以后第二行代码以前,CLR发生了一次垃圾回收,那么能够基本判定那个Cat对象实例已经不存在了,此时weakReference.Target是null。优化

WeakReference类型还有一个构造函数的重载为:this

Public WeakReference(Object target, bool trackResurrection)

其中bool类型的参数trackResurrection指定了这个WeakReference实例是一个长弱引用仍是一个短弱引用。对于短弱引用,当它所指向的对象被垃圾回收机制标记为“不可达”状态(即将被回收)时,该弱引用的Target属性即为null。而对于长弱引用,当它所指向对象的析构函数被调用以后,它的Target属性仍然是有效的。spa

弱引用的内部实现

弱引用看起来很神奇,彷佛是凌驾于正常的垃圾回收机制之上的,它到底是如何实现的呢?其实WeakReference类型在内部封装了一个名为GCHandle的struct类型,正是这个GCHandle使弱引用成为可能。.net

CLR中的每一个AppDomain都拥有一个GC句柄表。这个表的每一项记录有两个信息,一个是指向堆中某个对象的指针,另外一个是这个表项的类型。总共有4种表项类型,其中Weak和WeakTrackResurrection两种类型和咱们今天所讨论的弱引用相关。GCHandle这个类提供了一些操纵GC句柄表的方法。咱们可使用它的Alloc方法向GC句柄表中添加一个指定类型的表项。当垃圾回收开始后,垃圾回收器找到全部可达对象(简单的说,就是有用的对象)。而后遍历GC句柄表中每一个Weak类型的表项,若是发现某表项所指的对象不属于可达对象,则会把该表项的对象指针设置为null。紧接着,垃圾回收器会找出全部不可达对象中定义了析构函数的对象,并把他们放到一个被称为freachable的队列中(freachable中的对象会等待一个CLR中特定的线程来调用他们的终结函数)。因为这些freachable中的对象如今又被freachable队列所引用,因此它们又成为可达对象了。此时,垃圾回收器会遍历GC句柄表中全部WeakTrackResurrection类型的表项,和刚才同样,若是某表项所指的对象不属于可达对象,则会把该表项的对象指针设置为null。此处需注意,对于那些一开始被断定为不可达且定义了析构函数的对象来讲,它们在GC句柄表中所对于的表项指针仍然不是null。这就是Weak和WeakTrackResurrection两种类型的区别。线程

WeakReference就是经过表示了某个GC句柄表表项的GCHandle对象来完成跟踪对象生命周期的功能的。你也必定能够看出短弱引用利用了Weak类型的GC句柄表项,而长弱引用则利用了WeakTrackResurrection类型的表项。指针

WeakReference的一些注意事项

首先,WeakReference自身也实现了析构函数。也就是说,它即便再也不被使用了,也不会被当即回收,而是会在内存里赖着多活一会(可能会经历不止一次的垃圾回收)。

另外,如上一节所说,WeakReference会向GC句柄表添加一个表项。而每次垃圾回收,GC句柄表都会被遍历一遍。可想而知,若是系统中存在大量的WeakReference,那么GC句柄表极可能也会很是庞大,致使垃圾回收的效率下降。

WeakReference常常会和缓存联系起来,可是它并不适和用来实现一个大型的缓存机制。这是为何呢?一方面如前文所述,WeakReference自身实现了析构函数,也有可能致使垃圾回收的效率下降,所以应该避免在内存中建立大量的WeakReference对象实例。另外一方面,咱们知道一个对象若是没有被任何强引用所指向,而仅仅被弱引用所指向,那么它颇有可能活不过一次垃圾回收。因此经过这样的方式所实现出来的缓存机制势必有着很是短促的缓存策略,而这种策略在大部分状况下都不会是你指望获得的。

WeakReference的三个使用场景

对象缓存

试想这样一个场景,我有一个内存受限的程序,在这个程序里常常会使用一个占用不少内存的位图对象,所幸生成这个位图对象并不复杂。因此我每次要使用那个位图对象的时候都会从新生成它,使用完毕以后就将其丢弃(不保留它的引用)。

这种方式彻底可以知足个人需求,可是还能不能再优化呢?分析一下咱们就能够发现,当我须要使用位图对象的时候,我上次使用的那个位图对象虽然被我丢弃了,但可能仍然没有被垃圾回收,仍然存在内存中。此时若是我能直接使用这个位图对象,就能够节省出因重建位图对象而浪费的内存和CPU资源。

改进措施很简单——使用完位图对象后,不是直接丢弃,而是用一个弱引用指向它。待下次访问位图对象时,就能够先经过弱引用判断位图对象是否还在内存中,若是还在则直接使用,不然从新建立。

 辅助调试

有时,对于某种类型,咱们须要知道当前程序中存在有多少对象实例,以及存在的都是哪些实例,以便于咱们进行一些性能分析。这时,咱们就可使用到弱引用了。例以下面的代码: 

public class A
{
    private static List<WeakReference> _instances = new List<WeakReference>();

    public A()
    {
        _instances.Add(new WeakReference(this));
    } 

    public static int GetInstanceCount()
    {
        GC.Collect();
        return _instances.Count(x => x.Target != null);
    }
}

 GetInstanceCount方法能够获得内存中A类型的实例个数。另外,还能够经过instances集合来检查内存中的A类型实例都有哪些。在调试内存泄露问题的时候,这些信息均可以派上用场。

相比于各类性能分析Profiler工具,这种方法更加轻巧便捷。 

弱事件

.Net中的事件有时会引发内存泄露问题。例如,A注册了B的某个事件,此时B就会暗中保留A的一个强引用,致使A没法被内存回收,直到B被回收或A反注册了B的事件。例如,我有一个对象注册了主窗口的Loaded事件,只要我不反注册该事件,那么主窗口会一直引用该对象,直到主窗口被关闭,该对象才会被回收。因此,每当咱们注册某个对象的事件时,都有可能在不经意间埋下内存泄露的隐患。

解决这个问题的根本方法是,在必要的时候进行事件的反注册。可是,在某些状况下,咱们可能很难断定这个“必要的时候”。另外,当咱们做为类库的提供者时,咱们也很难保证类库的使用者都记得要反注册事件。所以,另外一个解决方案就是使用弱事件。

弱事件的实现原理很简单,就是对事件进行一层封装。不让事件发布者直接引用监听者,而是让他们保留一个监听者的弱引用。当事件触发时,发布者会先检查监听者是否还存在于内存中,若是存在才通知它。如此一来,监听者的生命周期就不会依赖于发布者了。

相关文章
相关标签/搜索