为什么在将50,000个对象插入到HashMap中时会出现OutOfMemoryError?

问题描述:

我试图插入大约50,000个对象(因此50,000个密钥)到java.util.HashMap<java.awt.Point, Segment>。但是,我不断收到OutOfMemory异常。 (Segment是我自己的班级 - 体重非常轻 - 一个String字段,以及3个int字段)。无论是在免费的RAM和硬盘空间用于虚拟内存 -为什么在将50,000个对象插入到HashMap中时会出现OutOfMemoryError?

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 
    at java.util.HashMap.resize(HashMap.java:508) 
    at java.util.HashMap.addEntry(HashMap.java:799) 
    at java.util.HashMap.put(HashMap.java:431) 
    at bus.tools.UpdateMap.putSegment(UpdateMap.java:168)

,因为我看到有大量内存的机器上可用这似乎挺可笑的。

是否有可能Java运行时有一些严格的内存要求?我可以增加这些吗?

HashMap有一些奇怪的限制吗?我将不得不执行我自己的?还有其他类别值得关注吗?

(我一个Intel机器有2GB RAM上运行的Java 5下OS X 10.5。)

可以增加通过传递-Xmx128m(其中,128是兆字节的数量)到Java堆大小的最大值。我不记得默认尺寸,但它让我觉得这是一个相当小的东西。

您可以通过使用Runtime类以编程方式检查有多少内存可用。

// Get current size of heap in bytes 
long heapSize = Runtime.getRuntime().totalMemory(); 

// Get maximum size of heap in bytes. The heap cannot grow beyond this size. 
// Any attempt will result in an OutOfMemoryException. 
long heapMaxSize = Runtime.getRuntime().maxMemory(); 

// Get amount of free memory within the heap in bytes. This size will increase 
// after garbage collection and decrease as new objects are created. 
long heapFreeSize = Runtime.getRuntime().freeMemory(); 

(来自实施例Java Developers Almanac

这也部分地解决Frequently Asked Questions About the Java HotSpot VM,并在Java 6 GC Tuning page

+0

如何确定当前的尺寸,以便我了解未来?谢谢! – 2008-10-24 19:57:26

+0

非常奇怪,虽然你有这样的小内存可用,你不能添加50000个小对象到散列。听起来不像那么多。 – 2008-10-24 19:59:07

+0

谢谢!把它抽到2048MB,我的程序终于完成了!哈哈。哇。 – 2008-10-24 20:05:22

可能也想看看这个:

http://java.sun.com/docs/hotspot/gc/

你可能需要启动Java时,设置标志-Xmx512m或一些较大的数字。我认为64mb是默认值。

修改为添加: 在找出对象实际使用的分析器的内存量之后,您可能需要查看弱引用或软引用,以确保您不会意外地持有一些内存中的人质从垃圾收集器中,当你不再使用它们时。

隐含在这些答案中,Java具有固定的内存大小,并且不会超出配置的最大堆大小。这不像C说的那样,它只受到它运行的机器的约束。

默认情况下,JVM使用有限的堆空间。限制取决于JVM的实现,并不清楚您使用的是什么JVM。在Windows以外的操作系统上,具有2 Gb或更多计算机的32位Sun JVM将使用缺省最大堆大小,即物理内存的1/4,或512 Mb。但是,“客户端”模式JVM的默认值仅为64 Mb最大堆大小,这可能是您遇到的问题。其他供应商的JVM可能会选择不同的默认值。

当然,您可以明确指定堆限制,-Xmx<NN>m选项为java,其中<NN>是堆的兆字节数。

作为一个粗略的猜测,你的散列表应该只使用大约16 Mb,所以在堆上必须有一些其他的大对象。如果您可以在TreeMap中使用Comparable密钥,那将节省一些内存。

请参阅"Ergonomics in the 5.0 JVM"了解更多详情。

如果事先知道对象的数量,另一件要尝试的就是使用HashMap(int capacity,double loadfactor)构造函数,而不是使用默认值(16,0.75)的默认no-arg构造函数。如果HashMap中元素的数量超过(capacity * loadfactor),那么HashMap中的底层数组将被调整为下一个2的幂,并且该表将被重新映射。这个数组也需要一个连续的内存区域,例如,如果你从32768增加到65536大小的数组,你将需要256kB的内存空间。为了避免额外的分配和重新哈希处罚,从一开始就使用一个更大的哈希表。它也会减少你不会有足够大的内存区域以适应地图的可能性。

实现通常由数组支持。数组是固定大小的内存块。哈希映射实现首先将数据存储在一个给定容量的数组中,比如100个对象。

如果它填满了数组并且不停地添加对象,地图需要秘密增加它的数组大小。由于数组是固定的,所以它通过在内存中创建一个全新的数组,以及稍大的当前数组来实现。这被称为增长阵列。然后,旧数组中的所有项都被复制到新数组中,并且旧数组被解引用,希望它会被垃圾收集并在某个时刻释放内存。

通常情况下,通过将项目复制到更大阵列来增加地图容量的代码是造成此类问题的原因。有“愚蠢的”实现和聪明的实现,它们使用增长或加载因子,根据旧数组的大小来确定新数组的大小。有些实现会隐藏这些参数,有些则不会,所以您无法始终设置它们。问题是,当你无法设置它时,它会选择一些默认的加载因子,比如2.因此新阵列的大小是旧的两倍。现在你所谓的50k地图有一个100k的支持数组。

看看是否可以将负载系数降至0.25或其他值。这会导致更多的哈希映射冲突,这会影响性能,但是您正在遇到内存瓶颈并需要这样做。

使用这个构造:

http://java.sun.com/javase/6/docs/api/java/util/HashMap.html#HashMap(int,浮动))

Java堆空间是默认的限制,但仍然听起来极端(虽然有多大的50000段)

?我怀疑你还有其他一些问题,比如集合中的数组越来越大,因为所有东西都被分配到同一个“槽”(当然也会影响性能)。但是,如果你的观点是均匀分布的,这似乎不太可能。

我想知道为什么你使用HashMap而不是TreeMap?即使点是二维的,你可以用比较函数对它们进行子类化,然后做log(n)查找。

有人建议改变HashMap的参数来加强内存需求。我建议的措施,而不是猜测;它可能是导致OOME的其他内容。特别是,我建议使用NetBeans ProfilerVisualVM(它随Java 6一起提供,但我看到你被Java 5困住了)。

随机想法:与HashMap关联的哈希桶不是特别有效的内存。您可能想要尝试使用TreeMap作为替代方案,并查看它是否仍能提供足够的性能。