WeakReference 是不是提供良好的缓存?
Posted
技术标签:
【中文标题】WeakReference 是不是提供良好的缓存?【英文标题】:Does WeakReference make a good cache?WeakReference 是否提供良好的缓存? 【发布时间】:2010-10-30 03:13:38 【问题描述】:我有一个缓存,它使用对缓存对象的 WeakReferences 来使它们在内存压力的情况下自动从缓存中删除。我的问题是缓存对象在存储在缓存中后很快就被收集了。缓存在 64 位应用程序中运行,尽管仍有超过 4gig 的内存可用,但仍会收集所有缓存的对象(此时它们通常存储在 G2 堆中)。正如进程资源管理器所示,没有手动引发垃圾收集。
我可以应用哪些方法来延长对象的寿命?
【问题讨论】:
【参考方案1】:使用 WeakReferences 作为引用缓存对象的主要方法并不是一个好主意,因为正如 Josh 所说,您将受制于 WeakReference 和 GC 未来的任何行为变化。
但是,如果您的缓存需要任何类型的复活功能,则对待清除的项目使用 WeakReferences 很有用。当一个项目满足逐出标准时,而不是立即逐出它,您将其引用更改为弱引用。如果在 GC 之前有任何请求,您可以恢复其强引用,并且该对象可以再次存活。我发现这对于一些难以预测命中率模式且具有足够频繁“复活”的缓存很有用。
如果您有可预测的命中率模式,那么我会放弃 WeakReference 选项并执行显式驱逐。
【讨论】:
问题是,一旦我有一个缓存对象的强引用,我就有可能在将其设置为可收集之前耗尽内存。 这就是你的驱逐策略的工作。每个缓存都需要两个主要的东西......在缓存中注册实例的能力,以及根据一组规则确定应该驱逐哪些项目的后台进程。有许多现有的驱逐策略可能有效:LRU(最近最少使用......即最旧的淘汰),LFU(最不常用......即丢弃 10 次点击以支持 100 次点击的项目)等。您可以'不要缓存每一个被永久添加的对象......你必须添加一个后台线程来处理驱逐。 但是后台线程只会时不时地清理缓存,最终为时已晚。 这取决于你。它会根据您的需要经常清理它。或者,如果这不起作用,您可以使用线程信号来让它按照懒惰的时间表进行清理,并按需进行。例如,您可以使用 ManualResetEvent 让主缓存代码向其后台工作人员发出信号,告知其应立即唤醒并清除、重置事件并重新进入睡眠状态。你让我现在想知道你的缓存到底是什么以及有多少。如果您非常担心您的 CACHE 会比 64 位机器消耗更多的内存......听起来你还有其他的 问题。您需要考虑您的缓存以及您的缓存确实需要缓存的项目量,因为如果您消耗了进程在 64 位系统上的所有可用内存,那么就会出现严重错误。【参考方案2】:在一种情况下,基于WeakReference
的缓存可能很好:当类中某项的有用性取决于对其的引用时。在这种情况下,弱内部缓存可能很有用。例如,如果一个应用程序会反序列化许多大型不可变对象,其中许多对象预计是重复的,并且必须在它们之间执行许多比较。如果X
和Y
是对某些不可变类类型的引用,那么如果两个变量都指向同一个实例,测试X.Equals(Y)
将会非常快,但如果它们指向恰好相等的不同实例,则可能会非常慢。如果一个反序列化的对象碰巧与另一个已经存在引用的对象匹配,那么从字典中获取对该后一个对象的引用(需要一个缓慢的比较)可能会加快未来的比较。另一方面,如果它匹配字典中的一个项目,但字典是对该项目的 only 引用,则使用字典对象而不是简单地保留已读取的对象几乎没有优势在;可能没有足够的优势来证明比较的成本是合理的。对于实习缓存,一旦不存在对对象的其他引用,让WeakReferences
尽快失效将是一件好事。
【讨论】:
【参考方案3】:在 .net 中,从 GC 的角度来看,WeakReference 根本不被视为引用,因此任何只有弱引用的对象都将在下一次 GC 运行中被收集(用于适当的生成)。
这使得弱引用完全不适合缓存 - 正如您的经验所示。
您需要一个“真正的”缓存组件,而缓存最重要的一点是让驱逐策略(即关于何时从缓存中删除对象的规则)与您的应用程序的使用模式。
【讨论】:
【参考方案4】:不,WeakReference 对此并不好,因为垃圾收集器的行为会随着时间而改变,而且您的缓存不应依赖于今天的行为。此外,您无法控制的许多因素也会影响内存压力。
.NET 的缓存有很多实现。您可能会在 CodePlex 上找到十几个。我想您需要添加的内容是查看应用程序的当前工作集以将其用作清除触发器。
关于为何如此频繁地收集您的对象的另一个说明。 GC 非常积极地清理 Gen0 对象。如果您的对象的生命周期很短(直到对它的唯一引用是弱引用),那么 GC 正在尽其所能尽快清理。
【讨论】:
“CodePlex 上的一打” ...和框架中的一个。 ASP.NET Cache System.Web.Caching.Cache 可以在非 ASP.NET 应用程序中使用,并且非常强大。 Microsoft 文档说不建议在客户端应用程序中使用它(但没有真正说明原因),但我已经在各种应用程序中成功使用它。 我尝试了 HttpRuntime.Cache,但它并没有像我喜欢的那样工作。我不断地将项目添加到缓存中,很快就得到了 OutOfMemoryException,而不是让缓存逐出项目。 "HttpRuntime.Cache,但它不像我喜欢的那样工作...... OutOfMemoryException"。令人惊讶,但您可以尝试调整缓存配置 - 例如privateBytesLimit 和 percentPhysicalMemoryUsedLimit 属性。或者 OutOfMemoryException 可能还有其他原因。【参考方案5】:我相信您遇到的问题是垃圾收集器会删除弱引用的对象,而不是仅响应内存压力 - 相反,它有时会非常积极地进行收集,只是因为运行时系统认为某些对象可能已变得无法访问。
您可能最好使用例如System.Runtime.Caching.MemoryCache,可以配置内存限制,或者为项目自定义驱逐策略。
【讨论】:
【参考方案6】:答案实际上取决于您尝试构建的缓存的使用特性。我已经成功地使用基于 WeakReference 的缓存策略来提高我的许多项目的性能,在这些项目中,缓存对象预计将用于短时间的多次读取。正如其他人指出的那样,从 GC 的角度来看,弱引用几乎是垃圾,并且会在下一个 GC 周期运行时被收集。与内存使用无关。
但是,如果您需要一个能够在 GC 的残酷行为中幸存下来的缓存,则需要使用或模仿 System.Runtime.Caching 命名空间提供的功能。请记住,当内存使用量超过您的阈值时,您需要一个额外的线程来清理缓存。
【讨论】:
【参考方案7】:有点晚了,但这里有一个相关的用例:
我需要缓存两种类型的对象:大型(反序列化)数据文件,加载需要 10 分钟,每个需要 15G 内存,以及包含对这些数据文件的内部引用的较小(动态编译)对象(较小的对象也被缓存,因为它们需要大约 10 秒才能生成)。这些缓存隐藏在提供对象的工厂中(前者组件不知道后者),并具有不同的驱逐策略。
当我的“数据文件”缓存驱逐一个对象时,它会用弱引用替换它,因此如果该对象在下一次请求时仍然可用,我们可以将其复活(并更新其缓存超时)。通过这种方式,我们避免在任何对象真正失效(即未在其他任何地方使用)之前丢失(或意外复制)任何对象。请注意,两个缓存都不需要知道另一个,并且没有其他客户端对象需要知道有任何缓存(例如:我们避免需要“keepalives”、回调、注册、检索和返回范围等 - 事情变得简单多了)。
因此,尽管单独使用 WeakReference(而不是缓存)是一个糟糕的想法(因为现代 GC 通常会根据 L2 CPU 缓存的大小进行调整,并且常规代码每分钟会烧掉很多次),但它非常作为一种在其余代码中隐藏缓存的方法非常有用。
【讨论】:
以上是关于WeakReference 是不是提供良好的缓存?的主要内容,如果未能解决你的问题,请参考以下文章
JAVA中的强引用软引用(SoftReference)弱引用(WeakReference)和幽灵引用(PhantomReference)