【发布时间】:2011-10-27 12:16:27
【问题描述】:
编辑:我已经重新组织了这个问题,以反映此后可用的新信息。
此问题基于Viliam 对 Guava Maps 使用惰性驱逐的问题的回答:Laziness of eviction in Guava's maps
请先阅读这个问题及其回复,但基本上得出的结论是 Guava 地图不会异步计算和强制驱逐。给定以下地图:
ConcurrentMap<String, MyObject> cache = new MapMaker()
.expireAfterAccess(10, TimeUnit.MINUTES)
.makeMap();
在访问一个条目后十分钟后,它仍然不会被驱逐,直到地图再次被“触摸”。执行此操作的已知方法包括常用访问器 - get() 和 put() 和 containsKey()。
我的问题的第一部分[已解决]:还有哪些其他调用会导致地图被“触摸”?具体来说,有谁知道size() 是否属于这一类?
想知道这一点的原因是我已经实现了一个计划任务,偶尔会使用这个简单的方法来微调我用于缓存的 Guava 地图:
public static void nudgeEviction() {
cache.containsKey("");
}
不过,我还使用cache.size() 以编程方式报告地图中包含的对象数量,以确认此策略是否有效。但是我无法从这些报告中看到差异,现在我想知道size() 是否也会导致驱逐发生。
答案: 所以 Mark 指出,在第 9 版中,驱逐仅由 get()、put() 和 replace() 方法调用,这可以解释为什么我没有看到containsKey() 的效果。这显然会随着即将发布的下一个版本的番石榴而改变,但不幸的是我的项目的发布设置得更早。
这让我陷入了一个有趣的困境。通常我仍然可以通过调用get("") 来触摸地图,但我实际上使用的是计算地图:
ConcurrentMap<String, MyObject> cache = new MapMaker()
.expireAfterAccess(10, TimeUnit.MINUTES)
.makeComputingMap(loadFunction);
其中loadFunction 加载与数据库中的键对应的MyObject。看起来我在 r10 之前没有简单的方法可以强制驱逐。但即使是能够可靠地强制驱逐,我的问题的第二部分也令人怀疑:
我的问题的第二部分 [已解决]: 作为反应 to one of the responses to the linked question,触摸地图是否可靠地驱逐所有过期条目?在链接的答案中,Niraj Tolia 另有说明,表示驱逐可能仅分批处理,这意味着可能需要多次调用触摸地图以确保所有过期对象都被驱逐。他没有详细说明,但这似乎与基于并发级别将地图分割成段有关。假设我使用了 r10,其中 containsKey("") 确实调用了驱逐,那么这将适用于整个地图,还是仅适用于其中一个片段?
答案: maaartinus 已经解决了这部分问题:
注意
containsKey和其他读取方法只运行postReadCleanup,它只会在每次第 64 次调用时执行任何操作(请参阅 DRAIN_THRESHOLD)。此外,看起来所有清理方法都只适用于单个 Segment。
所以看起来调用containsKey("") 不是一个可行的修复方法,即使在 r10 中也是如此。这将我的问题简化为标题:我怎样才能可靠地强制驱逐发生?
注意:我的 Web 应用程序明显受此问题影响的部分原因是,当我实现缓存时,我决定使用多个地图 - 一个用于我的数据对象的每一类。所以这个问题有可能是某个区域的代码被执行,导致一堆Foo对象被缓存,然后Foo缓存很长一段时间都没有被再次触及,所以它不会被驱逐任何事物。与此同时,Bar 和Baz 对象正在从其他代码区域缓存,并且内存正在被占用。我在这些地图上设置了最大尺寸,但这充其量只是一个脆弱的保护措施(我假设它的效果是立竿见影的 - 仍然需要确认这一点)。
更新 1:感谢 Darren 链接相关问题 - 他们现在有我的投票。所以看起来解决方案正在酝酿中,但似乎不太可能出现在 r10 中。与此同时,我的问题仍然存在。
更新 2: 在这一点上,我只是在等待 Guava 团队成员对我整理的 hack maaartinus 提供反馈(请参阅下面的答案)。
最新更新:已收到反馈!
【问题讨论】:
-
@Darren 感谢您指出这些