弱引用
GC在回收时检测对象是否有强引用,如果没有则可以执行回收。
那么什么是强引用的对象?简单概括说就是程序当前可以访问的对象。举两个例子
GlobalConfig=null
,强引用就消失了var user=new User()
;new User()这个对象就会被强引用,想要去除他的强引用,设置user=null
,或者在函数Method1()执行完成后该强引用也会消失创建回收简单,但是占用大量内存的对象,大对象
除了WeakReference他还有个泛型类WeakReference,两者只是提供的Api有些差别
我习惯用泛型类,下面就用泛型类来继续介绍了
var weakReference = new WeakReference<Article>(new Article() { Id = 1},true)
构造函数接收两个参数
获取弱引用对象的强引用
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