2010-07-09 116 views
8

我想使用MapMaker创建缓存大对象的映射,如果内存不足,应从缓存中删除这些大对象, 。 这个小演示程序似乎很好地工作:使用MapMaker创建缓存

public class TestValue { 
    private final int id; 
    private final int[] data = new int[100000]; 

    public TestValue(int id) { 
     this.id = id; 
    } 

    @Override 
    protected void finalize() throws Throwable { 
     super.finalize(); 
     System.out.println("finalized"); 
    } 
} 


public class Main { 

    private ConcurrentMap<Integer, TestValue> cache; 
    MemoryMXBean memoryBean; 

    public Main() { 
     cache = new MapMaker() 
       .weakKeys() 
       .softValues() 
       .makeMap(); 
     memoryBean = ManagementFactory.getMemoryMXBean(); 
    } 

    public void test() { 
     int i = 0; 
     while (true) { 
      System.out.println("Etntries: " + cache.size() + " heap: " 
       + memoryBean.getHeapMemoryUsage() + " non-heap: " 
       + memoryBean.getNonHeapMemoryUsage()); 
      for (int j = 0; j < 10; j++) { 
       i++; 
       TestValue t = new TestValue(i); 
       cache.put(i, t); 
      } 
      try { 
       Thread.sleep(100); 
      } catch (InterruptedException ex) { 
      } 
     } 
    } 

    /** 
    * @param args the command line arguments 
    */ 
    public static void main(String[] args) { 
     Main m = new Main(); 
     m.test(); 
    } 

} 

然而,当我做同样的事情在我的实际应用中,条目 基本上从缓存,尽快为他们添加删除。在我的真实 应用程序中,我也使用整数作为键,并且缓存值为 从包含某些数据的磁盘读取的归档块。据I 明白,只要不再使用 就会被垃圾收集垃圾收集,所以这似乎是有意义的,因为这些键是弱的 引用。如果我创建的地图是这样的:

data = new MapMaker() 
      .softValues() 
      .makeMap(); 

的条目是从来没有垃圾收集和我在测试程序得到了内存不足的错误 。 TestValue条目 上的finalize方法永远不会被调用。如果我改变了测试方法如下:

public void test() { 
    int i = 0; 
    while (true) { 
     for (final Entry<Integer, TestValue> entry : 
      data.entrySet()) { 
      if (entry.getValue() == null) { 
       data.remove(entry.getKey()); 
      } 
     } 
     System.out.println("Etntries: " + data.size() + " heap: " 
      + memoryBean.getHeapMemoryUsage() + " non-heap: " 
      + memoryBean.getNonHeapMemoryUsage()); 
     for (int j = 0; j < 10; j++) { 
      i++; 
      TestValue t = new TestValue(i); 
      data.put(i, t); 
     } 
     try { 
      Thread.sleep(100); 
     } catch (InterruptedException ex) { 
     } 
    } 
} 

条目从缓存中,并在测试值 对象终结被称为删除,但过了一段时间我也得到一个内存不足, 错误。

所以我的问题是:什么是正确的方式来使用MapMaker创建一个 地图,可以用作缓存?为什么我的测试程序不能尽快删除 条目,如果我使用weakKeys?是否有可能将 添加引用队列到缓存映射?

+0

有人可以编辑代码,使其更容易阅读? – nanda 2010-07-09 10:27:34

+0

我对此有点惊讶。我已经使用'softValues'完全相同的方式,并且它工作正常,当内存不足时,SoftReference被清除。 – finnw 2010-07-17 00:27:33

回答

3

弱键似乎是一个错误。尝试使用强键,因为它们是整数。

+0

我试过了,它在我创建一个新对象并将其添加到缓存之前调用System.gc()时有效。如果我不这样做,我迟早会得到一个内存不足的例外。这是正确的方法,还是你推荐别的? – Michael 2010-07-09 12:32:57

+0

你有两个版本的TestValue,一个拥有一个大数组,另一个只拥有一个int。你在用大阵列测试吗?否则,GC可能无法释放足够的内存。 – 2010-07-09 12:47:16

8

有很多事情可能会发生,但对于使用软值的测试程序:即使您有尚未被垃圾收集的SoftReference,也可能会发生OutOfMemoryError。需要重申的是,即使您有尚未清除的SoftReference,也可能会发生OutOfMemoryError。

SoftReferences有点奇怪,请参阅http://jeremymanson.blogspot.com/2009/07/how-hotspot-decides-to-clear_07.html了解当前机制。可能在您的测试案例中,GC没有时间做两个完整的GC。

当您使用弱键时,CG立即清除它们,并且不必等待完整的GC暂停。 (B/C在WeakReferences正在积极收集。)

在我看来,如果你想与整数密钥的内存敏感的缓存,我认为以下是合适的:

data = new MapMaker().softValues().makeMap(); 

您可以轻松地进行一个抛出OutOfMemoryError的测试程序,但是如果你真正的应用程序有一定的表现,并且没有受到太大的压力,那么你可能会好起来的。 SoftReferences很难找到正确的。

如果您需要使用System.gc()避免内存不足,我建议您切换到固定最大大小的LRU映射(例如,请参阅java.util.LinkedHashMap的javadoc。 )它并不是并发的,但我希望它最终能够让您获得更好的吞吐量,而不是要求系统在一堆额外的时间内完成全部暂停垃圾回收。

噢,还有关于整数键和弱键()的最后说明:当使用弱键或软键时,MapMaker对键使用标识比较,这很难正确执行。见证如下:

Map<Integer,String> map = new MapMaker().weakKeys().makeMap(); 
Integer a = new Integer(1); 
Integer b = new Integer(1); 
Integer c = 1; //auto box 
Integer d = 1; //auto box 
map.put(a, "A"); 
map.put(b, "B"); 
map.put(c,"C"); 
map.put(d,"D"); 
map.size() // size is 3; 

祝你好运。

+0

+1,因为我没有意识到你可以在所有的SoftReference被gc'ed之前得到一个OutOfMemoryError。 – 2010-08-06 17:06:27