Java中,空HashMap空间分配

Posted

技术标签:

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

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

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

【问题讨论】:

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

【讨论】:

以上是关于Java中,空HashMap空间分配的主要内容,如果未能解决你的问题,请参考以下文章

java中内存分配策略及堆和栈的比较

Java中成员变量分配在哪个空间?

java分配内存空间

如何使用命令行为JAVA分配空间[重复]

JAVA里String数组在内存分配中分配的空间每个占几个字节?

java程序是在编译的时候分配空间的吗,如果不是那程序在啥时候给变量分配内存空间?