集合中的内存消耗

时间:2017-11-26 10:02:51

标签: java memory-management collections

嗨最近在接受采访时,我被问到我有一个Hashmap,一个ArrayList和一个Hashset。它们每个包含相同的10个用户定义对象(例如:Employee类对象)。哪个会占用更多的堆空间?为什么?

我给出了Hashmap的答案,因为它存储了两个键值对。但是Hashset在内部也使用hashmap来存储值。

  1. 有人可以请理智。
  2. 我可以使用任何工具或eclipse插件自行查看吗?
  3. 感谢。

2 个答案:

答案 0 :(得分:3)

如果要计算容器所需的内存和" 10用户定义的对象所需的内存"那你是对的。

HashMap会占用更多空间。

即使HashSetHashMap支持,它在其所有条目中存储的值也是对同一虚拟实例的引用。

因此HashSet需要10个关键引用+10个值引用+10个元素+ 1个虚拟实例。

另一方面,HashMap需要10个关键引用+10个值引用+10个键实例+10个值实例(假设" 10个用户定义的对象"存储为值)。

当然,为了更准确,您还必须计算包含HashMap存储桶的数组的大小,但HashMapHashSet中的数据相同,因为它们包含相同数量的元素。

请注意,正如JB Nizet评论的那样,如果HashMap的密钥是" 10个用户定义的对象"的属性,那么" 10个密钥实例"不需要额外的内存(因为它们已经存在),因此HashMapHashSet都需要相同数量的内存来存储10个对象,而HashSet实际上需要一点更多内存,因为它包含对HashMap的引用。

ArrayList应该比HashSetHashMap占用更少的内存,因为ArrayList的后备数组的默认初始长度为10(这足够了)存储10个对象),而HashMap的存储区数组的默认初始长度为16(也足以存储10个对象,假设我们使用的默认加载因子为0.75)。

答案 1 :(得分:2)

我发现这非常有趣,虽然我在这里同意伊兰,但需要适当的证明。我正在使用JOL for the measuring

出于示例的目的,我创建了一个Employee,其中包含两个字段String nameint age

让我们看看发生了什么:

List<Employee> list = new ArrayList<>();
list.add(new Employee(22, "a"));

System.out.println(GraphLayout.parseInstance(list).totalSize()); //152 bytes

让我们看看这个空间的来源:

12 bytes ArrayList headers
4 bytes int modCount in ArrayList
4 bytes int size in ArrayList
4 bytes for the reference "elementData" in ArrayList

12 bytes for the Employee headers
4 bytes int age Employee
4 bytes for String name reference 
4 bytes padding (objects are 8 bytes aligned)

12 bytes for the String "a" headers
4 bytes for the char[] reference 
4 bytes for the int hash
4 bytes padding

12 bytes for the new char[] { 'a' }
4 bytes the size of the array (store in headers)
2 bytes for char 'a'
6 bytes padding

40 bytes for the 10 references in elementData array
12 bytes for it's headers (arrays are Objects)
4 bytes for the size (arrays have a size)

为了示例,我接下来将添加2名员工,并保持关于尺寸缩短的说明:

HashMap<Employee, Integer> map = new HashMap<>();
map.put(new Employee(22, "a"), 100);
map.put(new Employee(23, "b"), 200);

System.out.println(GraphLayout.parseInstance(map).toFootprint()); 

你会得到这样的输出:

  COUNT       AVG       SUM   DESCRIPTION
     2        24        48   [C
     1        80        80   [Ljava.util.HashMap$Node;
     2        16        32   java.lang.Integer
     2        24        48   java.lang.String
     1        48        48   java.util.HashMap
     2        32        64   java.util.HashMap$Node
     2        24        48   org.erabii.tenelemdiff.Test$Employee
    12                 368   (total)

总大小为368个字节。现在让我们将它们放入HashSet

HashSet<Employee> set = new HashSet<>();
set.add(new Employee(22, "a"));
set.add(new Employee(23, "b"));

System.out.println(GraphLayout.parseInstance(set).totalSize()); // 368 bytes

您可以看到HashSetHashMap的大小与此特定方案相同。如果您添加超过12个条目(默认情况下为HashMap重新调整大小)并且可能会将其从LinkedNode更改为{{1},那么事情会变得更加棘手s和差异is quite significant, read more hereTreeNode是32个字节,而Node是56个字节。