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

我试图将大约50,000个对象(因此50,000个键)插入到java.util.HashMap 。 但是,我一直得到一个OutOfMemoryexception。 ( Segment是我自己的类 – 非常轻的重量 – 一个String字段和3个int字段)。

 线程“main”中的exceptionjava.lang.OutOfMemoryError:Java堆空间
    在java.util.HashMap.resize(HashMap.java:508)
    在java.util.HashMap.addEntry(HashMap.java:799)
    在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。)

您可以通过将-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的示例)

关于Java HotSpot VM的常见问题以及Java 6 GC Tuning页面中也部分解决了这个问题 。

有些人建议更改HashMap的参数以收紧内存需求。 我建议测量而不是猜测 ; 它可能是造成OOME的其他因素。 特别是,我建议使用NetBeans Profiler或VisualVM (随Java 6一起提供,但我看到你坚持使用Java 5)。

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

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

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

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

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

使用此构造函数:

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

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

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

也可能想看看这个:

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

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

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

当然,您可以使用-Xmxm选项显式指定堆限制,其中是堆的兆字节数。

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

有关详细信息,请参阅“5.0 JVM中的人机工程学” 。

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

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

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

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