WeakReference是否可以提供良好的缓存?

时间:2009-05-30 17:46:36

标签: c# .net caching garbage-collection weak-references

我有一个缓存,它使用WeakReferences来缓存对象,以便在内存压力的情况下自动从缓存中删除它们。我的问题是缓存的对象在存储在缓存中后很快就会被收集。缓存在64位应用程序中运行,尽管仍然有超过4gig的内存可用,但所有缓存的对象都被收集(它们通常在那时存储在G2堆中)。过程资源管理器显示没有手动引发的垃圾收集。

我可以采用哪些方法让对象更长寿?

7 个答案:

答案 0 :(得分:14)

使用WeakReferences作为引用缓存对象的主要方法并不是一个好主意,因为正如Josh所说,您将对WeakReference和GC的任何未来行为变化摆布。

但是,如果您的缓存需要任何类型的复活功能,则对待处理清除的项目使用WeakReferences非常有用。当项目符合驱逐标准,而不是立即驱逐它时,您将其引用更改为弱引用。如果有什么东西在GC之前请求它,你恢复它的强引用,并且对象可以再次生存。我发现这对于一些难以预测命中率模式的缓存很有用,并且频繁的“复活”是有益的。

如果你有可预测的命中率模式,那么我会放弃WeakReference选项并执行明确的驱逐。

答案 1 :(得分:5)

在.net中,WeakReference根本不被视为GC观点的引用,因此任何只有弱引用的对象将在下一次GC运行中收集(适当的生成)。

这使得弱引用完全不适合缓存 - 正如您的经验所示。

你需要一个“真正的”缓存组件,而关于缓存的最重要的事情是得到一个驱逐策略(即关于何时从缓存中删除对象的规则)与你的应用程序的良好匹配使用模式。

答案 2 :(得分:5)

有一种情况,基于WeakReference的缓存可能是好的:当类中项的有用性基于对它的引用的存在时。在这种情况下,弱的实习缓存可能是有用的。例如,如果有一个应用程序将反序列化许多大型不可变对象,其中许多预期是重复的,并且必须在它们之间进行许多比较。如果XY是对某些不可变类类型的引用,那么如果两个变量指向同一个实例,则测试X.Equals(Y)会非常快,但如果它们指向不同的实例,则可能会非常慢碰巧是平等的。如果反序列化对象碰巧匹配已存在引用的另一个对象,则从字典中获取对后一个对象的引用(需要一个慢速比较)可以加快将来的比较。另一方面,如果它匹配字典中的项目但字典是对该项目的引用,则使用字典对象而不是简单地保持读取的对象几乎没有优势在;可能没有足够的优势来证明比较的成本。对于实习缓存,一旦对象不存在其他引用,WeakReferences尽快失效将是一件好事。

答案 3 :(得分:3)

不,WeakReference对此不利,因为垃圾收集器的行为可以并且将随着时间的推移而改变,并且您的缓存不应该依赖于今天的行为。您控制之外的许多因素也会影响记忆压力。

.NET的缓存有很多实现。您可以在CodePlex上找到十几个。我想你需要添加的内容是查看应用程序当前工作集以将其用作清除的触发器。

还有一个关于为什么频繁收集物品的说明。 GC非常积极地清理Gen0对象。如果你的对象非常短暂(直到它的唯一引用是一个弱引用)那么GC正在通过尽可能快地清理来完成它的设计。

答案 4 :(得分:1)

我相信你遇到的问题是,垃圾收集器会在响应内存压力的情况下删除弱引用的对象而不是 - 相反,它会因为运行时系统的思考而非常积极地进行收集某些物体可能无法到达。

你最好使用例如System.Runtime.Caching.MemoryCache,可配置内存限制或项目的自定义驱逐策略。

答案 5 :(得分:0)

答案实际上取决于您尝试构建的缓存的使用特征。我已经成功地使用了基于WeakReference的缓存策略来提高我的许多项目的性能,其中缓存对象应该用于多次读取的短突发。正如其他人所指出的那样,从GC的角度来看,弱引用几乎是垃圾,并且每当下一个GC周期运行时都会收集。这与内存利用率无关。

但是,如果您需要一个能够从GC中保持这种残酷性的缓存,则需要使用或模仿System.Runtime.Caching命名空间提供的功能。请记住,当内存使用率超过阈值时,您需要一个额外的线程来清理缓存。

答案 6 :(得分:0)

有点晚了,但这是一个相关的用例:

我需要缓存两种类型的对象:大型(反序列化)数据文件,每个加载需要10分钟,每个花费15G ram,以及包含对这些数据文件的内部引用的较小(动态编译)对象(较小的对象)也被缓存,因为他们需要大约10秒来生成)。这些缓存隐藏在提供对象的工厂中(前一个组件不了解后者),并且有不同的驱逐策略。

当我的`数据文件'缓存驱逐一个对象时,它会用弱引用替换它,所以如果该对象在下次请求时仍然可用,我们可以复活它(并更新它的缓存超时)。通过这种方式,我们可以避免在任何对象真正失效之前丢失(或意外复制)任何对象(即不在其他任何地方使用)。请注意,两个缓存都不需要知道另一个缓存,并且没有其他客户端对象需要知道有任何缓存(例如:我们避免需要'keepalive',回调,注册,检索和返回范围等等 - 事情变得更简单了。)

因此,尽管单独使用WeakReference(而不是缓存)是一个糟糕的想法(因为现代GC通常调整到L2 CPU缓存的大小,并且常规代码每分钟会烧掉这么多次),它非常用作隐藏你的缓存与其余代码的方法。