14

我正在尝试将大约 50,000 个对象(因此 50,000 个键)插入到java.util.HashMap<java.awt.Point, Segment>. 但是,我不断收到 OutOfMemory 异常。(Segment是我自己的课-重量很轻-一个String字段和3个int字段)。

线程“主”java.lang.OutOfMemoryError 中的异常:Java 堆空间
    在 java.util.HashMap.resize(HashMap.java:508)
    在 java.util.HashMap.addEntry(HashMap.java:799)
    在 java.util.HashMap.put(HashMap.java:431)
    在 bus.tools.UpdateMap.putSegment(UpdateMap.java:168)

这似乎很荒谬,因为我看到机器上有大量可用内存——在可用 RAM 和用于虚拟内存的 HD 空间中。

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

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

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

4

10 回答 10

22

您可以通过将 -Xmx128m(其中 128 是兆字节数)传递给 java.util.Date 来增加最大堆大小。我不记得默认大小,但让我觉得它相当小。

您可以使用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 调优页面中也有部分解决。

于 2008-10-24T19:55:32.717 回答
7

有些人建议更改 HashMap 的参数以收紧内存要求。我建议测量而不是猜测;可能是其他原因导致 OOME。特别是,我建议使用NetBeans ProfilerVisualVM(Java 6 附带,但我看到您坚持使用 Java 5)。

于 2008-10-24T20:32:09.163 回答
4

如果您事先知道对象的数量,可以尝试的另一件事是使用 HashMap(int capacity,double loadfactor) 构造函数,而不是使用默认值 (16,0.75) 的默认无参数构造函数。如果 HashMap 中的元素数量超过(容量 * loadfactor),则 HashMap 中的底层数组将调整为 2 的下一个幂,并且表将被重新散列。该数组还需要一个连续的内存区域,例如,如果您从 32768 大小的数组翻倍到 65536 大小的数组,您将需要 256kB 的可用内存块。为避免额外的分配和重新散列惩罚,只需从一开始就使用更大的散列表。它还会减少您没有足够大的连续内存区域以适应地图的机会。

于 2008-10-24T20:15:57.667 回答
3

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

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

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

看看你是否可以将负载因子降低到 0.25 或其他东西。这会导致更多的哈希映射冲突,这会损害性能,但是您遇到了内存瓶颈并且需要这样做。

使用此构造函数:

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

于 2008-10-24T20:16:06.370 回答
2

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

编辑添加:在您通过分析器确定对象实际使用了多少内存之后,您可能需要查看弱引用或软引用,以确保您不会意外地从垃圾收集器中获取一些内存作为人质。你不再使用它们。

于 2008-10-24T20:04:05.553 回答
1

也可能想看看这个:

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

于 2008-10-24T19:57:56.597 回答
1

在这些答案中隐含的是,Java 具有固定的内存大小,并且不会超过配置的最大堆大小。这不像 C 语言,它只受运行它的机器的限制。

于 2008-10-24T20:09:28.740 回答
1

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

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

作为一个粗略的猜测,你的哈希表应该只使用大约 16 Mb,所以堆上肯定还有一些其他的大对象。如果您可以Comparable在 a 中使用一个键TreeMap,那将节省一些内存。

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

于 2008-10-24T20:14:16.687 回答
1

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

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

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

于 2008-10-24T20:23:48.947 回答
1

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

于 2008-10-26T03:47:34.983 回答