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

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

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

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

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

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

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

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

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

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

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

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

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

我相信你遇到的问题是垃圾收集器在响应内存压力时会删除弱引用的对象 – 相反,它会非常积极地进行收集,因为运行时系统认为某些对象可能已经无法访问。

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

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

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

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

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

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

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