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

时间:2008-10-24 19:53:40

标签: java hashmap out-of-memory

我正在尝试将约50,000个对象(以及因此50,000个键)插入java.util.HashMap<java.awt.Point, Segment>。但是,我一直得到一个OutOfMemory异常。 (Segment是我自己的课程 - 权重非常轻 - 一个String字段和3个int字段。

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)

这看起来非常荒谬,因为我发现机器上有足够的内存 - 无论是在免费RAM还是用于虚拟内存的高清空间。

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

HashMap是否有一些奇怪的限制?我是否必须实施自己的?还有其他值得关注的课程吗?

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

10 个答案:

答案 0 :(得分:21)

您可以通过将-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 VMJava 6 GC Tuning page中也部分解决了这个问题。

答案 1 :(得分:7)

有些人建议更改HashMap的参数以收紧内存需求。我建议衡量而不是猜测;它可能是造成OOME的其他因素。特别是,我建议使用NetBeans ProfilerVisualVM(Java 6附带,但我看到你被Java 5困住)。

答案 2 :(得分:3)

如果事先知道对象的数量,另一件事就是使用HashMap(int capacity,double loadfactor)构造函数而不是默认的no-arg,它使用默认值(16,0.75)。如果HashMap中的元素数量超过(capacity * loadfactor),那么HashMap中的基础数组将调整为下一个2的幂,表格将被重新处理。此阵列还需要一个连续的内存区域,例如,如果您从32768到65536大小的阵列加倍,则需要256kB的内存块。为了避免额外的分配和重新处罚,只需从一开始就使用更大的哈希表。它还会降低你不会有足够大的内存连续区域来适应地图的可能性。

答案 3 :(得分:3)

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

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

通常,通过将项目复制到更大的数组来增加地图容量的代码是导致此类问题的原因。有“哑”实现和智能实现,使用增长或负载因子,根据旧数组的大小确定新数组的大小。有些实现隐藏了这些参数,有些则没有,所以你不能总是设置它们。问题是当你无法设置它时,它会选择一些默认的加载因子,比如2.所以新数组的大小是旧数组的两倍。现在你所说的50k地图有一个100k的支持数组。

看看你是否可以将负载系数降低到0.25或更低。这会导致更多的哈希映射冲突,从而影响性能,但是你遇到了内存瓶颈,需要这样做。

使用此构造函数:

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

答案 4 :(得分:2)

启动java时,您可能需要设置标志-Xmx512m或更大的数字。我认为64mb是默认值。

编辑添加: 在你弄清楚你的对象实际使用的内存有多少内存后,你可能想要查看弱引用或软引用,以确保你没有意外地从垃圾收集器中拿走你的一些内存人质更长时间使用它们。

答案 5 :(得分:1)

也可能想看看这个:

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

答案 6 :(得分:1)

在这些答案中暗示Java具有固定的内存大小,并且不会超出配置的最大堆大小。这与C不同,它只受到运行它的机器的约束。

答案 7 :(得分:1)

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

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

粗略猜测,您的哈希表应该只使用大约16 Mb,因此堆上必须有一些其他大型对象。如果您可以在Comparable中使用TreeMap密钥,那么可以节省一些内存。

有关详细信息,请参阅"Ergonomics in the 5.0 JVM"

答案 8 :(得分:1)

默认情况下Java堆空间有限,但这听起来仍然极端(虽然你的50000段有多大?)

我怀疑你还有其他问题,例如集合中的数组变得太大,因为所有内容都被分配到相同的“插槽”中(当然也会影响性能)。但是,如果您的积分均匀分布,那似乎不太可能。

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

答案 9 :(得分:1)

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