使用MapMaker创建缓存

时间:2010-07-09 07:25:38

标签: guava

我想使用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();
    }

}

然而,当我在我的实际应用程序中执行相同的操作时,条目是 基本上在添加后立即从缓存中删除。在我的真实 应用程序,我也使用整数作为键,缓存的值是 存档块从包含一些数据的磁盘读取。就我而言 明白,弱引用会尽快被垃圾收集 不再使用,所以这似乎有道理,因为键很弱 引用。如果我像这样创建地图:

    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) {
        }
    }
}

条目将从缓存和TestValue上的终结器中删除 对象被调用,但过了一段时间我也得到了一个内存不足 错误。

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

3 个答案:

答案 0 :(得分:8)

可能会有很多事情发生,但是对于使用软值的测试程序:即使你有尚未进行垃圾回收的SoftReferences,你也可以获得OutOfMemoryError。这有点重复:即使你有尚未清除的SoftReferences,你也可以得到一个OutOfMemoryError。

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

当您使用weakKeys时,CG立即清除它们,并且不必等待完整的GC暂停。 (b / c WeakReferences被积极收集。)

在我看来,如果你想要一个带有整数键的内存敏感缓存,我认为以下是合适的:

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

你可以很容易地制作一个抛出OutOfMemoryError的测试程序,但是如果你的真实应用程序运行得很好,并且没有承受太大的压力,你可能会没问题。 SoftReferences很难做对。

如果您需要使用System.gc()避免内存不足,我建议您切换到具有固定最大大小的LRU映射(有关示例,请参阅java.util.LinkedHashMap的javadoc。)它不是并发的,但我希望它最终会给你提供更好的吞吐量,而不是要求系统多次进行完全暂停的垃圾收集。

哦,关于整数键和weakKeys()的最后一点说明:当使用弱键或软键时,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;
祝你好运。

答案 1 :(得分:3)

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

答案 2 :(得分:0)

我想提请您注意Suppliers.memoizeWithExpirationm,即时缓存。

http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/base/Suppliers.html#memoizeWithExpiration(com.google.common.base.Supplier,long,java.util.concurrent.TimeUnit)