【问题标题】:In Java, empty HashMap space allocationJava中,空HashMap空间分配
【发布时间】:2011-07-06 02:17:12
【问题描述】:

在添加任何元素之前,我如何知道预先确定大小的 HashMap 占用了多少空间?例如我如何确定以下占用多少内存?

HashMap<String, Object> map = new HashMap<String, Object>(1000000);

【问题讨论】:

  • 为什么,创建地图时不需要
  • @Travis:哇,冷静!我没有阻止他做任何事情,只是问他想做什么,以便我们可以更好地帮助他。
  • 抱歉代码错误。在使用不同的集合/地图管理大量数据时,我正在测试性能。快速插入快速检索基于出色的哈希算法且无需重新哈希。
  • hashmap 不是现在的结构,现在没有什么大的应该由单核管理;回答您的问题 - 每个条目 1048576*8+ 48。请记住 HashMap 会“扰乱”位并使用 pow2 表(无素数),因此散列函数可能不是很好。放置时的分配总体而言是高昂的成本,无论是占用空间还是性能(由于额外的间接性,在获取时也是如此)

标签: java hashmap memory-management


【解决方案1】:

您可能可以使用像 VisualVM 这样的分析器并跟踪内存使用情况。

也看看这个:http://www.velocityreviews.com/forums/t148009-java-hashmap-size.html

【讨论】:

    【解决方案2】:

    我想看看这篇文章:http://www.javaworld.com/javaworld/javatips/jw-javatip130.html

    简而言之,java 没有 C 风格的 sizeof 运算符。您可以使用分析工具,但 IMO 上面的链接提供了最简单的解决方案。

    另一条可能有用的信息:一个空的 java 字符串占用 40 个字节。其中一百万可能至少有 40MB...

    【讨论】:

    • 另外,我相信哈希映射分配的内部表大于请求的大小。我相信它通过找到比您请求的大小大 2 的最小幂来做到这一点。
    【解决方案3】:

    您可以在创建变量之前和之后检查内存使用情况。例如:

    long preMemUsage = Runtime.getRuntime().totalMemory() -
          Runtime.getRuntime().freeMemory();
    HashMap<String> map = new HashMap<String>(1000000);
    long postMemUsage = Runtime.getRuntime().totalMemory() -
          Runtime.getRuntime().freeMemory();
    

    【讨论】:

    • 为了使其更可靠: - 在测量内存之前运行 System.gc() 10 次 - 构建 1000 个表并取平均值
    • 我发布的链接解释了为什么这种方法不准确
    【解决方案4】:

    您应该能够使用 VisualVM(JDK 6 附带或可以是 downloaded)创建内存快照并检查分配的对象的大小。

    【讨论】:

      【解决方案5】:

      我同意分析器确实是唯一的判断方法。其他相关信息是您使用的是 32 位还是 64 位 JVM。由于内存引用(指针)而产生的开销取决于您是否打开了压缩 oops。我发现对于较小的数据集,对象和指针的开销很大。

      【讨论】:

        【解决方案6】:

        确切答案取决于您使用的 Java 版本、JVM 供应商和目标平台,最好通过直接测量来确定,如其他答案中所述。

        但作为一个简单的估计,大小可能是 ~4 * 2^20~8 * 2^20 字节,分别用于 32 位或 64 位 jvm。

        推理:

        • HashMap 的 Sun Java 1.6 实现有一个固定侧的顶级对象和一个指向哈希链引用数组的 table 字段。

        • 在一个新创建的(空)HashMap 中,引用都是null,并且数组大小是提供的initialCapacity 的第二次幂。 (是的......我检查了源代码。)

        • 一个引用在典型的 32 位 JVM 上占用 4 个字节,在典型的 64 位 JVM 上占用 8 个字节。一些 64 位 JVM 支持紧凑引用(“compressed oops”),但您需要设置 JVM 选项以启用此功能。

        • 顶部对象有 5 个字段,包括 table 数组引用,但这是一个相对较小的常量开销。

        • 顶部对象和数组有对象头开销,但这些开销是恒定的且相对较小。

        因此,table 数组的大小占主导地位,它是 2^20(大于 1,000,000 的 2 的下一个幂)乘以引用的大小。


        因此,这告诉您设置较大的初始容量确实会占用大量内存。另一方面,如果初始容量是完全填充时地图容量的良好估计值,则通过设置它可以节省大量时间。 (这避免了重新分配数组和重建哈希链的多次循环。)

        【讨论】:

          【解决方案7】:

          原则上,您可以:

          • 理论计算:
            • 查看 HashMap 的实现,了解此方法的作用。
            • 查看 VM 的实现以了解各个创建的对象占用了多少空间。
          • 以某种方式测量它。

          其他大部分答案都是关于第二种方式的,所以我会看第一种(在 OpenJDK 源代码中,1.6.0_20)。

          构造函数使用capacity,它是二的下一个幂 >= 您的 initialCapacity 参数,因此在我们的例子中是 1048576 = 2^20。 然后它创建一个new Entry[capacity] 并将其分配给table 变量。 (另外它分配了一些原始变量)。

          所以,我们现在有一个非常小的 HashMap 对象(它只包含 3 个整数、一个浮点数和一个引用变量)和一个非常大的 Entry[] 对象。这个数组需要空间来存放它们的数组元素(它们是普通的引用变量)和一些元数据(大小、类)。

          所以,这取决于参考变量有多大。这取决于 VM 实现 - 通常在 32 位 VM 中是 32 位(= 4 字节),在 64 位 VM 中是 64 位(= 8 字节)。

          因此,基本上在 32 位 VM 上,您的阵列需要 4 MB,在 64 位 VM 上需要 8 MB,再加上一些微小的管理数据。


          如果您随后使用映射填充 HashTable,则每个映射对应于一个 Entry 对象。此条目对象由一个 int 和三个引用组成,在 32 位 VM 上占用大约 24 个字节,在 64 位 VM 上可能是两倍。因此,您的 1000000 映射 HashMap(假设负载因子 > 1)在 32 位 VM 上将占用 ~28 MB,在 64 位 VM 上占用 ~56 MB。

          当然,除了键和值对象本身。

          【讨论】:

            【解决方案8】:

            在最新版本的 Java 1.7(我正在查看 1.7.0_55)中,HashMap 实际上延迟实例化其内部表。它仅在调用 put() 时实例化 - 请参阅私有方法“inflateTable()”。因此,至少在向其添加任何内容之前,您的 HashMap 将只占用少数几个字节的对象开销和实例字段。

            【讨论】:

              猜你喜欢
              • 2012-03-26
              • 2012-08-08
              • 1970-01-01
              • 2015-04-30
              • 2012-09-28
              • 2017-09-12
              • 2011-09-11
              • 2021-05-21
              • 2014-04-18
              相关资源
              最近更新 更多