如何计算Java中的HashMap内存使用情况?

ins*_*iac 23 java garbage-collection jvm memory-management hashmap

我在接受采访时被要求计算内存使用量HashMap以及如果你有200万个项目它会消耗多少估计内存.

例如:

Map <String,List<String>> mp=new HashMap <String,List<String>>();
Run Code Online (Sandbox Code Playgroud)

映射是这样的.一个键作为字符串,一个字符串数组作为键.

key   value
----- ---------------------------
abc   ['hello','how']
abz   ['hello','how','are','you']
Run Code Online (Sandbox Code Playgroud)

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

Pet*_*rey 22

简短的回答

为了找出对象的大小,我会使用一个分析器.例如,在YourKit中,您可以搜索对象,然后让它计算其深度.如果对象是独立的并且对象是保守的大小,这将让您充分了解将使用多少内存.

狡辩

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

序列化怎么样?

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

例如

在Sun/Oracle的JVM中,整数可以占用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 []时,消耗的内存为每个字符1个字节.;)注意:对于4字符串,如示例所示,由于8字节边界,使用的大小相同.

"大小"是什么意思?

正如已经指出的那样,HashMap和List更复杂,因为很多(如果不是全部)可以重用Strings,可能是String文字.你所说的"大小"取决于它的使用方式.即结构单独使用多少内存?如果结构被丢弃,将释放多少?如果复制结构,将使用多少内存?这些问题可以有不同的答案.

没有探查器你能做什么?

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

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

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

  • YourKit 可免费评估。买之前我已经有好几个了。有时在一个职业中,为你使用的工具付费是值得的。;) (2认同)

J.M*_*nny 6

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

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

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

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

还有loadFactor、modCount、threshold和size,它们都是原始int类型(多16个字节)和头(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 字节。现在使用字符串,没有自动装箱,并且字符串不再那么小,因此它不会那么大。 (3认同)