.Net性能调优-WeakReference

概述

弱引用

GC在回收时检测对象是否有强引用,如果没有则可以执行回收。

那么什么是强引用的对象?简单概括说就是程序当前可以访问的对象。举两个例子

  • 某个类里定义了一个静态变量GlobalConfig,那这个GlobalConfig就是被强引用的对象,如果设置GlobalConfig=null,强引用就消失了
  • 在任意函数Method1()中,某一行执行一段代码var user=new User();new User()这个对象就会被强引用,想要去除他的强引用,设置user=null,或者在函数Method1()执行完成后该强引用也会消失

适用场景

创建回收简单,但是占用大量内存的对象,大对象

不适用场景

  1. 对象的结构复杂,弱引用对象内部的资源可能被销毁等,比如弱引用一个MQHelper,MQHelper又有一个属性Connection,Connection可能被释放了,但是MQHelper仍然被弱引用

  2. 对象更新频率过高 ,高于弱引用对象的销毁周期,并且业务无法忍受这种延迟。可通过在更新对象时设置弱引用对象为null或者更新弱引用的值来解决

  3. 维护空弱引用会长期占用过多的资源。比如建立一个字典Dictionary<id,WeakReference>存了一百万个用户的弱引用,即使所有的弱引用的对象都回收了,这个字典也会长期保持一百万个id的键和WeakReference对象本身

除了WeakReference他还有个泛型类WeakReference,两者只是提供的Api有些差别

我习惯用泛型类,下面就用泛型类来继续介绍了

创建弱引用

var weakReference = new WeakReference<Article>(new Article() { Id = 1},true)

构造函数接收两个参数

  • Target: 引用的对象
  • trackResurrection: 如果为true,并且对象实写了析构函数,则该对象在没有被强引用之后可以存活过一次GC

获取弱引用对象的强引用

var isSuccess= weakReference.TryGetTarget(out Article article)

重新设置弱引用的对象

weakReference.SetTarget(article)

怎样理解这个trackResurrection呢?请看代码

public class Article
{
    public Article() => Console.WriteLine("create new Article");
    public int Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    ~Article()
    {
        Console.WriteLine("#################################");
    }
}

WeakReference<Article> weakReference = new WeakReference<Article>(new Article() { Id = 1, Title = "title", Description = "desc" }, true);
public void TraceTest()
{
    if (IsArticleAlive())
    {
        Console.WriteLine("article1 is alive");
    }
    GC.Collect();
    GC.WaitForPendingFinalizers();

    if (IsArticleAlive())
    {
        Console.WriteLine("article2 is alive");
    }
    else
    {
        Console.WriteLine("article2 is null");
    }

    GC.Collect();
    GC.WaitForPendingFinalizers();

    if (IsArticleAlive())
    {
        Console.WriteLine("article3 is alive");
    }
    else
    {
        Console.WriteLine("article3 is null");
    }
}
public bool IsArticleAlive() => weakReference.TryGetTarget(out Article article);

执行结果:

create new Article
article1 is alive
#################################
article2 is alive
article3 is null

可以看到在第一次GC回收时,执行了Article的析构函数,但是article仍然存活,直到第二次GC执行之后,article被回收。如果设置trackResurrection为false,则article2 is null.

如果我的析构函数里将Title变为了null呢?下次获取到的article的Title就是null了,所以说弱引用的对象需要是一个简单的对象,连IDisposable都没有实现的对象

应用

据说弱引用事件是弱引用最适合的场景,但是并没有发现很好的实现方式,要么就是过于的复杂,暂时就不研究他了,大概说下为什么事件适合弱引用

比如我有一个Publisher用来发布事件,Consumer订阅事件。那么每次订阅事件的时候,Publisher的事件都会保存一个回调函数。

如果回调函数里又引用了Consumer本身的成员变量,那么创建100个Consumer,Publisher就会包含一百个回调函数,同时这100个Consumer也不会释放。当然正常的写法我们会在Consumer用完的时候用Event的-=移除事件。弱引用的作用就是防止Consumer忘记移除了,最终造成内存溢出

我这里想到的场景是用弱引用来保存数据库或其他存储区的大对象,更像是一种内存缓存的用法,但是与内存缓存不同的是,它的生命周期不可控制,不会影响GC的回收。

上代码

private static ConcurrentDictionary<int, WeakReference<Article>> _cache = new ConcurrentDictionary<int, WeakReference<Article>>();
  
public Article GetArticleByWeakReference(int id)
{
    bool created = false;
    var weakRef = _cache.GetOrAdd(id, i =>
    {
        created = true;
        Console.WriteLine("created " + i);
        return new WeakReference<Article>(_articleRepository.GetArticle(i));
    });

    weakRef.TryGetTarget(out Article article);

    //如果弱引用的对象已被回收,则重新获取对象填充
    if (article == null)
    {
        Console.WriteLine("article " + id + " is null-----------------------------");
        article = _articleRepository.GetArticle(id);
        weakRef.SetTarget(article);
    }
    else
    {
        //弱引用之前获取过,但是引用的对象已被回收
        if (!created)
            Console.WriteLine("article" + id + " is not null");
    }
    return article;
}

需要注意的是,如果Article过多,会让_cache本身变得过大,需要综合考虑

第二点就是Article在更新的时候,需要更新弱引用的对象weakRef.SetTarget(article),也可以考虑只把Article中占用空间最大的Description做弱引用。查询的时候先判断Description是否存在,再考虑拼接查询条件是否同时查出Description还是只需要查询id和name

posted @ 2021-09-13 22:06  张三~~  阅读(701)  评论(1编辑  收藏  举报