Java 可靠地迫使番石榴地图驱逐发生

Java 可靠地迫使番石榴地图驱逐发生,java,caching,guava,Java,Caching,Guava,编辑:我重新组织了这个问题,以反映自那时以来获得的新信息 本问题基于对有关Guava Maps使用偷懒驱逐的问题的回答: 请先阅读这个问题及其回答,但基本上结论是番石榴地图不会异步计算和强制驱逐。根据以下地图: ConcurrentMap<String, MyObject> cache = new MapMaker() .expireAfterAccess(10, TimeUnit.MINUTES) .makeMap(); 但是,我也在使用cache

编辑:我重新组织了这个问题,以反映自那时以来获得的新信息

本问题基于对有关Guava Maps使用偷懒驱逐的问题的回答:

请先阅读这个问题及其回答,但基本上结论是番石榴地图不会异步计算和强制驱逐。根据以下地图:

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());
      }
   }
}