【问题标题】:Why do I get an OutOfMemoryError when inserting 50,000 objects into HashMap?为什么将 50,000 个对象插入 HashMap 时出现 OutOfMemoryError?
【发布时间】:2010-09-19 02:25:48
【问题描述】:

我正在尝试将大约 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。)

【问题讨论】:

    标签: java hashmap out-of-memory


    【解决方案1】:

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

    【讨论】:

    • 有意思,能不能详细介绍一下这个凯文?
    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

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

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

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

        【讨论】:

          【解决方案5】:

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

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

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

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

          使用这个构造函数:

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

          【讨论】:

            【解决方案6】:

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

            【讨论】:

              【解决方案7】:

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

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

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

              更多详情请见"Ergonomics in the 5.0 JVM"

              【讨论】:

              • 提升上限已经成功,但非常感谢您对 TreeMap 的引用。
              【解决方案8】:

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

              【讨论】:

              • @Frank Krueger:做出这个选择是为了实现更高效的垃圾收集器。一个固定的最大尺寸有助于优化这个东西。
              【解决方案9】:

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

              Frequently Asked Questions About the Java HotSpot VMJava 6 GC Tuning page 也部分解决了这个问题。

              【讨论】:

              • 如何确定当前尺寸以便了解未来?谢谢!
              • 很奇怪,虽然您的可用内存如此之少,以至于您无法将 50000 个小对象添加到哈希中。听起来不像那么多。
              • 谢谢!将它抽到 2048 MB,我的程序终于完成了执行!哈哈。哇。
              • 我不得不同意艾伦的观点——2048 MB 似乎有点过分。您可能想使用分析器来查看所有这些分配的来源。
              • 当然我可以使用分析器并处理散列函数来减少内存使用量,但这个工具每月运行一次或两次。我的时间最好花在优化产品而不是支持工具上。但是感谢您的建议!
              【解决方案10】:

              也可能想看看这个:

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-03-18
                • 1970-01-01
                • 2015-07-14
                • 2016-03-21
                • 1970-01-01
                相关资源
                最近更新 更多