【问题标题】:Using MapMaker to create a cache使用 MapMaker 创建缓存
【发布时间】:2010-07-09 07:25:38
【问题描述】:

我想使用 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,请尽快输入条目?是否有可能 向缓存映射添加引用队列?

【问题讨论】:

  • 有人可以编辑代码以使其更易于阅读吗?
  • 对此我有点惊讶。我以完全相同的方式使用softValues,它运行良好,当内存不足时,SoftReference 被清除。

标签: guava


【解决方案1】:

可能会发生很多事情,但对于使用软值的测试程序:即使您有尚未被垃圾回收的 SoftReference,您也可能会得到 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 的测试程序,但是如果您的实际应用程序表现得有些好,并且没有承受太大的压力,那么您可能会没事的。软引用很难正确。

如果您需要使用 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 因为我没有意识到在所有 SoftReference 被 gc'ed 之前你会得到 OutOfMemoryError。
【解决方案2】:

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

【讨论】:

  • 我试过了,如果我在创建新对象并将其添加到缓存之前调用 System.gc(),它会起作用。如果我不这样做,我迟早会遇到内存不足的异常。这是正确的方法还是您推荐其他方法?
  • 您有两个版本的 TestValue,一个包含一个大数组,一个只包含一个 int。您是否正在使用大型阵列进行测试?如果没有,则 GC 可能无法释放足够的内存。
【解决方案3】:

我想提请您注意 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)

【讨论】:

  • 酷!但是没有提到RefereceTypes ==没有保证引用将在内存错误之前被释放
猜你喜欢
  • 2011-11-08
  • 2015-09-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-02
  • 1970-01-01
相关资源
最近更新 更多