19

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

我可以应用哪些方法来使对象的寿命更长一些?

4

7 回答 7

17

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

但是,如果您的缓存需要任何类型的复活功能,则对待清除的项目使用 Wea​​kReferences 很有用。当一个项目满足逐出标准时,而不是立即逐出它,您将其引用更改为弱引用。如果在 GC 之前有任何请求,您可以恢复其强引用,并且该对象可以再次存活。我发现这对于一些难以预测命中率模式且具有足够频繁“复活”的缓存很有用。

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

于 2009-05-30T18:26:14.243 回答
6

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

于 2012-10-30T03:24:00.237 回答
5

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

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

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

于 2009-05-30T19:57:57.133 回答
3

不,WeakReference 对此不好,因为垃圾收集器的行为会随着时间而改变,并且您的缓存不应该依赖于今天的行为。您无法控制的许多因素也可能会影响内存压力。

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

关于为什么您的对象被如此频繁地收集的另一个注意事项。GC 非常积极地清理 Gen0 对象。如果您的对象的生命周期非常短暂(直到对它的唯一引用是弱引用),那么 GC 正在尽其所能尽快清理。

于 2009-05-30T17:50:03.723 回答
2

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

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

于 2013-09-16T14:49:58.397 回答
0

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

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

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

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

于 2015-02-21T02:08:54.507 回答
0

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

但是,如果您需要一个能够在 GC 中幸存下来的缓存,您需要使用或模仿 System.Runtime.Caching 命名空间提供的功能。请记住,当内存使用量超过您的阈值时,您需要一个额外的线程来清理缓存。

于 2013-05-15T04:14:43.043 回答