【问题标题】:How to calculate HashMap memory usage in Java?如何计算 Java 中的 HashMap 内存使用量?
【发布时间】:2011-09-03 16:22:39
【问题描述】:

在一次采访中,我被要求计算 HashMap 的内存使用量,以及如果你有 200 万个项目,它将消耗多少内存。

例如:

Map <String,List<String>> mp=new HashMap <String,List<String>>();

映射是这样的。

key   value
----- ---------------------------
abc   ['hello','how']
abz   ['hello','how','are','you']

如何估计 Java 中这个 HashMap 对象的内存使用情况?

【问题讨论】:

  • 这取决于它包含什么...
  • 所以......你还没有做这个问题,是吗?这似乎有点像作弊......
  • @petar Minchev:它将值存储在地图中,他们希望我计算总内存使用量。这很令人困惑,因为它有一个键和多个值。
  • @josh.trow:这个问题是几天前在电话中问我的。
  • @Petar,hashmap 内存分配不是整个保留的大小,它是使用它的开销(即浅大小)

标签: java memory-management garbage-collection hashmap jvm


【解决方案1】:

我认为应该澄清这个问题,因为HashMap的大小和HashMap的大小+ HashMap所包含的对象之间存在差异。

如果您考虑 HashMap 的大小,在您提供的示例中,HashMap 存储一个对字符串“aby”的引用和一个对 List 的引用。所以列表中的多个元素无关紧要。值中仅存储对列表的引用。

在 32 位 JVM 中,在一个 Map 条目中,您有 4 个字节用于“aby”引用 + 4 个字节用于 List 引用 + 4 个字节用于 Map 条目的“hashcode” int 属性 + 4 个字节用于“下一个“地图条目的属性。

您还添加了 4*(X-1) 字节引用,其中“X”是当您调用构造函数 new HashMap&lt;String,List&lt;String&gt;&gt;() 时 HashMap 创建的空桶的数量 .根据http://docs.oracle.com/javase/6/docs/api/java/util/HashMap.html,应该是16。

还有 loadFactor、modCount、threshold 和 size,它们都是原始 int 类型(多 16 个字节)和 header(8 个字节)。

所以最后,您上面的 HashMap 的大小将是 4 + 4 + 1 + (4*15) + 16 + 8 = 93 个字节

这是基于 HashMap 拥有的数据的近似值。我认为面试官可能有兴趣了解您是否了解 HashMap 的工作方式(例如,默认构造函数为 Map 条目创建 16 个存储桶的数组,存储在 HashMap 中的对象的大小不影响 HashMap 的大小,因为它只存储引用)。

HashMap 应用如此广泛,以至于在某些情况下,应该值得使用具有初始容量和负载因子的构造函数。

【讨论】:

  • 您忽略了每个对象 8 个字节的内存管理开销,并舍入到大小的 8 个字节。例如,HashMap.Entry 是 24 字节,而不是 16。在许多情况下,这会浪费大量内存。例如,HashMap&lt;Integer, Double&gt; 由于装箱,每个存储值需要大约 100 字节,实际数据为 12 字节,开销为 88 字节。现在有了字符串,没有自动装箱,而且字符串没有那么小,所以不会那么大。
【解决方案2】:

简短回答

要了解对象有多大,我会使用分析器。例如,在 YourKit 中,您可以搜索对象,然后让其计算其深度大小。如果对象是独立的并且是对象的保守大小,这将使您大致了解将使用多少内存。

狡辩

如果对象的某些部分在其他结构中重复使用,例如字符串文字,您不会通过丢弃它来释放这么多内存。事实上,丢弃对 HashMap 的一个引用可能根本不会释放任何内存。

序列化呢?

序列化对象是获得估计值的一种方法,但由于序列化开销和编码在内存和字节流中不同,因此它可能会大打折扣。使用多少内存取决于 JVM(以及它是否使用 32/64 位引用),但序列化格式始终相同。

例如

在 Sun/Oracle 的 JVM 中,一个 Integer 可以占用 16 个字节的头部,4 个字节的数字和 4 个字节的填充(对象在内存中是 8 字节对齐的),总共 24 个字节。但是,如果你序列化一个整数,它需要 81 个字节,序列化两个整数,它们需要 91 个字节。即第一个 Integer 的大小被夸大了,而第二个 Integer 小于内存中使用的大小。

字符串是一个更复杂的例子。在 Sun/Oracle JVM 中,它包含 3 个 int 值和一个 char[] 引用。所以你可能会假设它使用 16 字节标头加上 3 * 4 字节用于ints,4 字节用于char[],16 字节用于char[] 的开销,然后每个字符两个字节,对齐到 8-字节边界...

哪些标志可以改变大小?

如果您有 64 位引用,则 char[] 引用的长度为 8 个字节,从而产生 4 个字节的填充。如果您有 64 位 JVM,则可以使用 +XX:+UseCompressedOops 来使用 32 位引用。 (所以单看 JVM 位大小并不能告诉你它的引用大小)

如果您有-XX:+UseCompressedStrings,JVM 将尽可能使用 byte[] 而不是 char 数组。这可以稍微减慢您的应用程序,但可以显着提高您的内存消耗。当使用 byte[] 时,每个 char 消耗的内存为 1 个字节。 ;) 注意:对于 4 字符字符串,如示例中所示,由于 8 字节边界,使用的大小相同。

“大小”是什么意思?

正如已经指出的那样,HashMap 和 List 更复杂,因为许多(如果不是全部)字符串可以重用,可能是字符串文字。 “大小”的含义取决于它的使用方式。即该结构单独使用多少内存?如果结构被丢弃,会释放多少?如果复制该结构将使用多少内存?这些问题可以有不同的答案。

没有分析器你能做什么?

如果您可以确定可能的保守尺寸足够小,则确切尺寸无关紧要。保守的情况可能是您从头开始构造每个字符串和条目。 (我只说可能是因为 HashMap 可以容纳 10 亿个条目,即使它是空的。具有单个字符的字符串可以是具有 20 亿个字符的字符串的子字符串)

您可以执行 System.gc(),获取可用内存,创建对象,执行另一个 System.gc(),然后查看可用内存减少了多少。您可能需要多次创建对象并取平均值。多次重复这个练习,但它可以给你一个公平的想法。

(顺便说一句,System.gc() 只是一个提示,Sun/Oracle JVM 默认每次都会执行一次 Full GC)

【讨论】:

  • YourKit 不是免费的,你可以使用 jmap (+jhat),它是 JDK 的一部分。
  • YourKit 在评估时是免费的。买之前有好几个。有时在一个职业中,为你使用的工具付出代价是值得的。;)
【解决方案3】:

如果不知道所有字符串是什么,每个列表中有多少项,或者不知道字符串是否都是唯一引用,则无法提前知道。

唯一确定的方法是将整个内容序列化为一个字节数组(或临时文件)并查看确切的字节数。

【讨论】:

  • @john:他们问我估计的内存使用情况。你知道如何计算吗?
  • 他们可能会问你这个问题,以便你问我们要问的所有问题。如果没有足够的信息,你就无法估计某些东西,到目前为止,仅仅一张字符串列表的地图是不够的。地图中的物品怎么可能?每个键的平均大小?每个键列表中的平均项目数?每个键列表中每个字符串的平均大小?等等……
  • @John 数据结构的内存 O() 大小 内存 估计每个元素的内存,java 中字符串的引用取决于架构,它在 4 到 8 个字节之间。什么字符串,本身。 contains 与数据结构无关。
  • @bestsss 这真的取决于您是在争论结构本身的浅尺寸,还是整个结构的深尺寸。如果问了这个面试问题,我会问它看看被面试者为了进行估计而问了什么问题,而不是详细讨论 sun/oracle 实现 HashMap 本身的内部结构.
  • @John,但是如果你 map.put(String.intern(), Boolean.valueOf(b)) 你会怎么做?我确实了解 String.intern() 可能(确实)使 perm-gen 膨胀,但是您敢打赌,您会在面试中从我那里得到类似的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-27
  • 2016-10-21
  • 1970-01-01
  • 2013-03-31
  • 2022-10-30
  • 2018-01-18
相关资源
最近更新 更多