• .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

  • 相关阅读:
    随机图片
    单页网站
    最安全的聊天工具——Cryptocat
    一个游戏——小黑屋
    SAO Utils – SAO风格启动菜单
    对话框实现
    抖动文字
    Leetcode: 22. Generate Parentheses
    Leetcode: 21. Merge Two Sorted Lists
    Leetcode: 20. Valid Parentheses
  • 原文地址:https://www.cnblogs.com/bluesummer/p/15265121.html
Copyright © 2020-2023  润新知