OutOfMemoryErrors甚至在使用WeakReference用于键和值之后

时间:2010-01-13 21:39:59

标签: java

以下是我编写的一个小测试,用于教育自己参考API。我以为这永远都不会扔给OOME,但它却扔了它。我无法弄清楚原因。感谢任何帮助。

public static void main(String[] args)
{
    Map<WeakReference<Long>, WeakReference<Double>> weak = new HashMap<WeakReference<Long>, WeakReference<Double>>(500000, 1.0f);

    ReferenceQueue<Long> keyRefQ = new ReferenceQueue<Long>();
    ReferenceQueue<Double> valueRefQ = new ReferenceQueue<Double>();

    int totalClearedKeys = 0;
    int totalClearedValues = 0;

    for (long putCount = 0; putCount <= Long.MAX_VALUE; putCount += 100000)
    {
        weak(weak, keyRefQ, valueRefQ, 100000);

        totalClearedKeys += poll(keyRefQ);
        totalClearedValues += poll(valueRefQ);

        System.out.println("Total PUTs so far    = " + putCount);
        System.out.println("Total KEYs CLEARED so far    = " + totalClearedKeys);
        System.out.println("Total VALUESs CLEARED so far = " + totalClearedValues);
    }

}

public static void weak(Map<WeakReference<Long>, WeakReference<Double>> m, ReferenceQueue<Long> keyRefQ,
        ReferenceQueue<Double> valueRefQ, long limit)
{
    for (long i = 1; i <= limit; i++)
    {
        m.put(new WeakReference<Long>(new Long(i), keyRefQ), new WeakReference<Double>(new Double(i), valueRefQ));
        long heapFreeSize = Runtime.getRuntime().freeMemory();
        if (i % 100000 == 0)
        {
            System.out.println(i);
            System.out.println(heapFreeSize / 131072 + "MB");
            System.out.println();
        }

    }
}

private static int poll(ReferenceQueue<?> keyRefQ)
{
    Reference<?> poll = keyRefQ.poll();
    int i = 0;
    while (poll != null)
    {
        // 
        poll.clear();
        poll = keyRefQ.poll();
        i++;
    }
    return i;

}

}

以下是使用64MB堆运行时的日志

Total PUTs so far    = 0
Total KEYs CLEARED so far    = 77982
Total VALUESs CLEARED so far = 77980
100000
24MB

Total PUTs so far    = 100000
Total KEYs CLEARED so far    = 134616
Total VALUESs CLEARED so far = 134614
100000
53MB

Total PUTs so far    = 200000
Total KEYs CLEARED so far    = 221489
Total VALUESs CLEARED so far = 221488
100000
157MB

Total PUTs so far    = 300000
Total KEYs CLEARED so far    = 366966
Total VALUESs CLEARED so far = 366966
100000
77MB

Total PUTs so far    = 400000
Total KEYs CLEARED so far    = 366968
Total VALUESs CLEARED so far = 366967
100000
129MB

Total PUTs so far    = 500000
Total KEYs CLEARED so far    = 533883
Total VALUESs CLEARED so far = 533881
100000
50MB

Total PUTs so far    = 600000
Total KEYs CLEARED so far    = 533886
Total VALUESs CLEARED so far = 533883
100000
6MB

Total PUTs so far    = 700000
Total KEYs CLEARED so far    = 775763
Total VALUESs CLEARED so far = 775762
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at Referencestest.weak(Referencestest.java:38)
    at Referencestest.main(Referencestest.java:21)

6 个答案:

答案 0 :(得分:4)

来自http://weblogs.java.net/blog/2006/05/04/understanding-weak-references

我认为你可能会使用HashMap。您可能想要使用WeakHashMap

  

解决“小部件序列号”   上面的问题,最简单的事情   是使用内置的WeakHashMap类。   WeakHashMap的工作方式与此类似   HashMap,除了键(不是键)   值!)是指使用弱   引用。如果是WeakHashMap键   变成垃圾,其条目被删除   自动。这避免了   我描述的陷阱,并要求不   除了切换之外的其他更改   HashMap到WeakHashMap。如果你是   遵循标准惯例   通过地图引用您的地图   接口,没有其他代码需要甚至   要意识到这种变化。

答案 1 :(得分:3)

问题的核心可能是你用WeakReference对象填充你的堆,当内存不足时,弱引用被清除,但引用对象本身不是,所以你的hashmap正在填满如果是WeakReference对象(更不用说hashmap使用的对象数组,它会无限增长),所有都指向null。

正如已经指出的那样,解决方案是一个弱的hashmap,如果这些对象不再被使用,它们将清除这些对象(这在put期间完成)。

修改:
正如凯文指出的那样,你已经完成了你的引用队列逻辑(我没有给予足够的关注),使用你的代码的解决方案就是在收集密钥的地方将其清除出地图。这正是弱哈希映射的工作原理(只需在插入时触发轮询)。

答案 2 :(得分:1)

即使你的弱引用放弃了他们引用的东西,它们仍然无法自行回收。

所以最终你的哈希会填满对任何事物的引用并崩溃。

你需要什么(如果你想这样做)将是一个由对象删除触发的事件,该事件进入并从哈希中删除了引用。 (这会导致您需要注意的线程问题)

答案 3 :(得分:0)

我根本不是一个java专家,但我知道在.NET中进行大量的大对象内存分配时,你可以将堆碎片放到只有很小的连续内存可供分配的地步,即使很多更多内存显示为“免费”。

快速谷歌搜索“java heap fragmentation”会带来一些看似相关的结果,虽然我没有好好看看它们。

答案 4 :(得分:0)

另一个问题可能是Java由于某种原因并不总是在耗尽memmory时激活其garbadge collecter,因此您可能需要插入显式调用来激活收集器。尝试像

这样的东西

if((putCount%1000)=== 0)    调用Runtime.getRuntime()GC();

你的循环中的

编辑:似乎来自sun的新java实现现在在抛出OutOfMemmoryException之前调用了garbadge收集器,但我很确定以下程序会使用jre1.3或1.4抛出OutOfMemmoryException

public class Test {     public static void main(String args []){     while(true){         byte [] data = new byte [1000000];     }     } }

答案 5 :(得分:0)

其他人已正确指出问题所在;例如@roe,@ Bill K。

但另一种解决此类问题的方法(除了挠头,询问SO等)之外,还要看看Sun推荐的方法是如何工作的。在这种情况下,您可以在WeakHashMap类的源代码中找到它。

有几种方法可以找到Java源代码:

  • 如果你有一个不错的Java IDE来运行,它应该能够向你展示类库中任何类的源代码。

  • 大多数J2SE JDK下载都包含(至少)公共API类的源JAR文件。

  • 您可以专门为基于OpenJDK的Java版本下载完整的源代码发行版。

但ZERO EFFORT方法是使用类的完全限定名称进行Google搜索,最后加上“.java.html”。例如,搜索“java.util.WeakHashMap.java.html”会在搜索结果的第一页中显示this link

源代码将告诉您标准的WeakHashMap实现显式轮询其引用队列以从地图的键集中清除陈旧(即损坏)的弱引用。实际上,每次访问或更新地图时,它都会这样做,甚至只是询问它的大小。