Java 可靠地迫使番石榴地图驱逐发生
编辑:我重新组织了这个问题,以反映自那时以来获得的新信息 本问题基于对有关Guava Maps使用偷懒驱逐的问题的回答: 请先阅读这个问题及其回答,但基本上结论是番石榴地图不会异步计算和强制驱逐。根据以下地图:Java 可靠地迫使番石榴地图驱逐发生,java,caching,guava,Java,Caching,Guava,编辑:我重新组织了这个问题,以反映自那时以来获得的新信息 本问题基于对有关Guava Maps使用偷懒驱逐的问题的回答: 请先阅读这个问题及其回答,但基本上结论是番石榴地图不会异步计算和强制驱逐。根据以下地图: ConcurrentMap<String, MyObject> cache = new MapMaker() .expireAfterAccess(10, TimeUnit.MINUTES) .makeMap(); 但是,我也在使用cache
ConcurrentMap<String, MyObject> cache = new MapMaker()
.expireAfterAccess(10, TimeUnit.MINUTES)
.makeMap();
但是,我也在使用cache.size()
以编程方式报告映射中包含的对象数,以此确认此策略是否有效。但是我还没有看到这些报告的区别,现在我想知道size()
是否也会导致驱逐
答案:因此Mark指出,在第9版中,驱逐仅由get()
、put()
和replace()
方法调用,这可以解释为什么我没有看到containsKey()
的效果。这显然会随着即将发布的下一个版本guava而改变,但不幸的是,我的项目发布时间提前了
这让我陷入了一个有趣的困境。通常我仍然可以通过调用get(“”
)来触摸地图,但实际上我使用的是计算地图:
ConcurrentMap<String, MyObject> cache = new MapMaker()
.expireAfterAccess(10, TimeUnit.MINUTES)
.makeComputingMap(loadFunction);
ConcurrentMap cache=newmapmaker()
.expireAfterAccess(10,时间单位:分钟)
.makeComputingMap(加载函数);
其中loadFunction
从数据库中加载与键对应的MyObject
。看起来在r10之前,我没有简单的方法强制驱逐。但我的问题的第二部分让人怀疑是否能够可靠地强制驱逐:
我问题的第二部分[已解决]:作为回应,触摸地图是否可靠地排除所有过期条目?在链接的回答中,表示不是这样,表示驱逐可能只会成批处理,这意味着可能需要多次调用来触摸地图,以确保所有过期对象都被驱逐。他没有详细说明,但是这似乎与基于并发级别将映射划分为段有关。假设我使用了r10,其中一个containsKey(“”
调用了逐出,那么这是针对整个地图,还是仅针对其中一个片段
答案:maaartinus已经回答了这部分问题:
请注意,
containsKey
和其他读取方法只运行postReadCleanup
,它只在每次第64次调用时执行(请参见DRAIN_THRESHOLD)。此外,看起来所有清理方法都只能处理单个线段
因此,即使在r10中,调用containsKey(“”)
似乎也不是一个可行的解决方案。这就把我的问题简化为:我怎样才能可靠地强制驱逐
注意:我的web应用程序明显受到此问题影响的部分原因是,当我实现缓存时,我决定使用多个映射-我的数据对象的每个类对应一个映射。因此,在这个问题上,可能会执行一个代码区域,导致缓存一堆Foo
对象,然后Foo
缓存很长一段时间内不会再次被触摸,因此不会逐出任何内容。与此同时,Bar
和Baz
对象正在从代码的其他区域缓存,内存正在被消耗。我正在设置这些地图的最大尺寸,但这充其量只是一个脆弱的保护措施(我假设它的效果是即时的——仍然需要确认)
更新1:感谢Darren将相关问题联系起来-他们现在有了我的选票。因此,看起来解决方案正在酝酿之中,但似乎不太可能在r10中实现。与此同时,我的问题仍然存在
更新2:此时此刻,我正在等待一位番石榴团队成员对我和maaartinus一起制作的黑客游戏给出反馈(见下面的答案)
上次更新:收到反馈 我想知道你在问题的第一部分描述的同一个问题。从番石榴的源代码可以看出,在
get()
、put()
和replace()
方法中,条目似乎被逐出。containsKey()
方法似乎没有调用逐出。我不是100%确定,因为我很快就通过了代码
更新:
我还在Guava的git存储库中发现了一个更新版本的,它看起来像containsKey()
已经更新为调用逐出
当调用size()
时,我刚刚发现的9版和最新版本都不会调用逐出
更新2:
我最近注意到番石榴r10
(尚未发布)有一个名为的新类。基本上,这个类是MapMaker
的分叉版本,但考虑到缓存。文档表明,它将支持您正在寻找的一些驱逐要求
我查看了r10版本中的更新代码,发现了一个类似于计划地图清理器的程序。不幸的是,现在代码似乎还没有完成,但r10看起来每天都越来越有希望。请注意,
containsKey
和其他读取方法只运行postReadCleanup
,这只会在第64次调用时执行(请参阅DRAIN_THRESHOLD)。此外,看起来所有清理方法都只能处理单个线段
强制逐出的最简单方法似乎是在每个段中放置一些虚拟对象。要使其工作,您需要分析CustomConcurrentHashMap.hash(Object)
,这肯定不是一个好主意,因为此方法可能随时更改。此外,根据密钥类的不同,可能很难找到具有哈希代码的密钥,以确保其位于给定的段中<
ConcurrentMap<String, MyObject> cache = new MapMaker()
.expireAfterAccess(10, TimeUnit.MINUTES)
.makeComputingMap(loadFunction);
public void runCleanup() {
final Segment<K, V>[] segments = this.segments;
for (int i = 0; i < segments.length; ++i) {
segments[i].runCleanup();
}
}
public class GuavaEvictionHacker {
//Class objects necessary for reflection on Guava classes - see Guava docs for info
private static final Class<?> computingMapAdapterClass;
private static final Class<?> nullConcurrentMapClass;
private static final Class<?> nullComputingConcurrentMapClass;
private static final Class<?> customConcurrentHashMapClass;
private static final Class<?> computingConcurrentHashMapClass;
private static final Class<?> segmentClass;
//MapMaker$ComputingMapAdapter#cache points to the wrapped CustomConcurrentHashMap
private static final Field cacheField;
//CustomConcurrentHashMap#segments points to the array of Segments (map partitions)
private static final Field segmentsField;
//CustomConcurrentHashMap$Segment#runCleanup() enforces eviction on the calling Segment
private static final Method runCleanupMethod;
static {
try {
//look up Classes
computingMapAdapterClass = Class.forName("com.google.common.collect.MapMaker$ComputingMapAdapter");
nullConcurrentMapClass = Class.forName("com.google.common.collect.MapMaker$NullConcurrentMap");
nullComputingConcurrentMapClass = Class.forName("com.google.common.collect.MapMaker$NullComputingConcurrentMap");
customConcurrentHashMapClass = Class.forName("com.google.common.collect.CustomConcurrentHashMap");
computingConcurrentHashMapClass = Class.forName("com.google.common.collect.ComputingConcurrentHashMap");
segmentClass = Class.forName("com.google.common.collect.CustomConcurrentHashMap$Segment");
//look up Fields and set accessible
cacheField = computingMapAdapterClass.getDeclaredField("cache");
segmentsField = customConcurrentHashMapClass.getDeclaredField("segments");
cacheField.setAccessible(true);
segmentsField.setAccessible(true);
//look up the cleanup Method and set accessible
runCleanupMethod = segmentClass.getDeclaredMethod("runCleanup");
runCleanupMethod.setAccessible(true);
}
catch (ClassNotFoundException cnfe) {
throw new RuntimeException("ClassNotFoundException thrown in GuavaEvictionHacker static initialization block.", cnfe);
}
catch (NoSuchFieldException nsfe) {
throw new RuntimeException("NoSuchFieldException thrown in GuavaEvictionHacker static initialization block.", nsfe);
}
catch (NoSuchMethodException nsme) {
throw new RuntimeException("NoSuchMethodException thrown in GuavaEvictionHacker static initialization block.", nsme);
}
}
/**
* Forces eviction to take place on the provided Guava Map. The Map must be an instance
* of either {@code CustomConcurrentHashMap} or {@code MapMaker$ComputingMapAdapter}.
*
* @param guavaMap the Guava Map to force eviction on.
*/
public static void forceEvictionOnGuavaMap(ConcurrentMap<?, ?> guavaMap) {
try {
//we need to get the CustomConcurrentHashMap instance
Object customConcurrentHashMap;
//get the type of what was passed in
Class<?> guavaMapClass = guavaMap.getClass();
//if it's a CustomConcurrentHashMap we have what we need
if (guavaMapClass == customConcurrentHashMapClass) {
customConcurrentHashMap = guavaMap;
}
//if it's a NullConcurrentMap (auto-evictor), return early
else if (guavaMapClass == nullConcurrentMapClass) {
return;
}
//if it's a computing map we need to pull the instance from the adapter's "cache" field
else if (guavaMapClass == computingMapAdapterClass) {
customConcurrentHashMap = cacheField.get(guavaMap);
//get the type of what we pulled out
Class<?> innerCacheClass = customConcurrentHashMap.getClass();
//if it's a NullComputingConcurrentMap (auto-evictor), return early
if (innerCacheClass == nullComputingConcurrentMapClass) {
return;
}
//otherwise make sure it's a ComputingConcurrentHashMap - error if it isn't
else if (innerCacheClass != computingConcurrentHashMapClass) {
throw new IllegalArgumentException("Provided ComputingMapAdapter's inner cache was an unexpected type: " + innerCacheClass);
}
}
//error for anything else passed in
else {
throw new IllegalArgumentException("Provided ConcurrentMap was not an expected Guava Map: " + guavaMapClass);
}
//pull the array of Segments out of the CustomConcurrentHashMap instance
Object[] segments = (Object[])segmentsField.get(customConcurrentHashMap);
//loop over them and invoke the cleanup method on each one
for (Object segment : segments) {
runCleanupMethod.invoke(segment);
}
}
catch (IllegalAccessException iae) {
throw new RuntimeException(iae);
}
catch (InvocationTargetException ite) {
throw new RuntimeException(ite.getCause());
}
}
}