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

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

例如:

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

映射是这样的。

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

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


答案 1

简短的回答

为了找出对象有多大,我会使用探查器。例如,在 YourKit 中,您可以搜索对象,然后让它计算其深度大小。这将使您公平地了解如果对象是独立的并且对象的大小保守,将使用多少内存。

诡计多端

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

那么序列化呢?

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

例如:

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

字符串是一个复杂得多的示例。在 Sun/Oracle JVM 中,它包含 3 个值和一个引用。因此,您可能会假设它使用16字节标头加3 * 4字节作为s,4字节用于,16字节用于开销,然后每个字符使用两个字节,对齐到8字节边界...intchar[]intchar[]char[]

哪些标志可以改变大小?

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

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

你说的“大小”是什么意思?

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

如果没有探查器,您可以执行哪些操作?

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

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

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


答案 2

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

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

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

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

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

因此,最终,上面的HashMap的大小将为4 + 4 + 1 + (4 * 15)+ 16 + 8 = 93字节

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

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