.Net性能调优-WeakReference
概述
弱引用
GC在回收时检测对象是否有强引用,如果没有则可以执行回收。
那么什么是强引用的对象?简单概括说就是程序当前可以访问的对象。举两个例子
- 某个类里定义了一个静态变量GlobalConfig,那这个GlobalConfig就是被强引用的对象,如果设置
GlobalConfig=null
,强引用就消失了 - 在任意函数Method1()中,某一行执行一段代码
var user=new User()
;new User()这个对象就会被强引用,想要去除他的强引用,设置user=null
,或者在函数Method1()执行完成后该强引用也会消失
适用场景
创建回收简单,但是占用大量内存的对象,大对象
不适用场景
-
对象的结构复杂,弱引用对象内部的资源可能被销毁等,比如弱引用一个MQHelper,MQHelper又有一个属性Connection,Connection可能被释放了,但是MQHelper仍然被弱引用
-
对象更新频率过高 ,高于弱引用对象的销毁周期,并且业务无法忍受这种延迟。可通过在更新对象时设置弱引用对象为null或者更新弱引用的值来解决
-
维护空弱引用会长期占用过多的资源。比如建立一个字典Dictionary<id,WeakReference
>存了一百万个用户的弱引用,即使所有的弱引用的对象都回收了,这个字典也会长期保持一百万个id的键和WeakReference对象本身
除了WeakReference他还有个泛型类WeakReference
我习惯用泛型类,下面就用泛型类来继续介绍了
创建弱引用
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