【问题标题】:How much memory Java HashSet<Long> should takeJava HashSet<Long> 应该占用多少内存
【发布时间】:2015-04-08 15:02:30
【问题描述】:

我想使用HashSet&lt;Long&gt; 在内存中存储大量唯一数字。我计算了要消耗的近似内存(以 64 位指针大小):

Long 将占用 16 个字节的空间。所以最初我将条目数乘以 16 来获得内存。但实际上,每个条目的内存远超过 16 个字节。之后我研究了HashSet 的实现。简而言之,在底层实现中,它实际上与 hashset 的每个条目一起存储了一个额外的虚拟对象(12 个字节)。以及指向下一个条目的指针(8 个字节)。从而让出每个条目额外的 12+8 字节。

所以每个条目的总内存:16+12+8 = 36 字节。但是当我运行代码并检查内存时,每个条目仍然远远超过 36 个字节。

我的问题(简而言之)HashSet 占用多少内存(例如,在 64 位机器上)?

【问题讨论】:

标签: java data-structures hashset


【解决方案1】:

您可以使用此测试准确测量此尺寸:

    long m1 = Runtime.getRuntime().freeMemory();
    // create object (s) here
    long m2 = Runtime.getRuntime().freeMemory();
    System.out.println(m1 - m2);

使用 -XX:-UseTLAB 选项运行

在我的 64 位 HotSpot 上,空 HashSet 占用 480 个字节。

为什么这么多?因为 HashSet 具有复杂的结构(btw IDE 在调试模式下有助于查看实际字段)。它基于 HashMap(适配器模式)。所以 HashSet 本身包含对 HashMap 的引用。 HashMap 包含 8 个字段。实际数据位于节点数组中。一个节点有: int hash; K键; V值;下一个节点。 HashSet 仅使用键并将虚拟对象放入值中。

【讨论】:

  • 这是一个很好的古玩思想的食物。我会试试然后回来
  • hmmm 这很有趣,从来不知道 Set 占用的空间比 Map跨度>
  • @nafas HashSet 在内部使用 HashMap。虽然所有的值都指向同一个虚拟对象
【解决方案2】:

对象的大小是一个实现细节。不能保证在一个平台上是 x 字节,在另一个平台上也是 x 字节。

Long 如您所知被装箱,但 16 个字节是错误的。原语 long 占用 8 个字节,但 long 周围的框的大小取决于实现。根据this Hotspot related answer开销字和填充意味着一个装箱的4字节int可以变成24字节!

该(特定于热点的)答案中提到的字节对齐和填充也适用于Entry 对象,这也会推动消耗。

【讨论】:

  • 因此,使用包装类的两个词惩罚(每个对象 16 个字节)是罪魁祸首。谢谢。
【解决方案3】:

使用的内存是 32 * SIZE + 4 * CAPACITY + ( 16 * SIZE ) 是“SIZE”元素的数量。

【讨论】:

  • 你的意思是 64 * SIZE?
  • 不,是 12 字节头 + 16 字节数据 + 4 字节填充
  • 你能解释一下这个公式吗:为什么我们用32乘以大小。容量为 4,大小为 16。
  • 请提供来源。
  • 我在前面的评论中说的前 32 个字节是每个条目的大小,4 个字节是容量,最后 16 个字节是 long 的值。更好的解释:java-performance.info/memory-consumption-of-java-data-types-2
【解决方案4】:

HashMap 默认大小为 16 个 HashMapEntry 条目。 每个 HashMapEntry 上有四个对象(int keyHash、Object next、Object key、Object value)。因此,它通过包装元素引入了仅用于具有空条目的开销。 此外,hashmap 的扩展率为 2 倍,因此对于 17 个元素,您将拥有 32 个条目,其中 15 个为空。

更简单的方法是使用内存分析器检查堆转储。

【讨论】:

    【解决方案5】:

    HashSet 是一只复杂的野兽。在我回顾了一些 cmets 之后,我突然想到,这里有一些你没有考虑到的消耗内存的项目:

    1. Java 集合(真正的集合,不是普通数组)只能采用对象引用,不能采用原语。因此,您的 long 原语被装箱到 java.lang.Long 对象中,并且添加到 HashSet. Somebody mentioned that aLong 对象的引用将是 24 个字节。加上引用,即 8 个字节。
    2. 哈希表存储桶是集合。我不记得它们是数组还是ArrayList,还是LinkedList等,但是因为散列算法可能会产生冲突,所以HashSet的元素必须放入集合中,这些集合由散列码组织。最好的情况是 ArrayList 只有一个元素:你的 Long 对象。 ArrayList 的默认后备数组大小为 10,因此对象中有 10 个对象引用,因此现在每个 Long 至少有 80 个字节。由于Long 是一个整数,我怀疑散列算法可以很好地分散事物。我不确定值超过 Integer.MAX_VALUE 的 long 会发生什么。由于生日悖论,这必须以某种方式发生冲突。
    3. 实际的哈希表 - HashSet 基本上是一个 HashMap,其中的值并不有趣。在底层,它创建了一个HashMap,其中有一个桶数组来表示哈希表。数组大小是根据容量来的,根据你添加的元素个数看不清楚。
    4. 散列表的大小通常会有意地拥有比需要更多的桶,以使未来的增长更容易。希望不会更多。但不要指望 5 个元素恰好占用 5 个桶。

    长话短说,哈希表是一种内存密集型数据结构。这是空间/时间的权衡。假设散列分布良好,您将获得恒定时间查找,但代价是额外的内存使用。

    【讨论】:

    • “哈希表桶是集合。” 哈希桶是一个链表,现在可能是一个树结构。它们是 Map.Entry 的包私有子类型,与我们可见的任何集合无关。
    猜你喜欢
    • 2014-05-30
    • 2021-12-25
    • 2023-04-03
    • 2011-02-20
    • 2017-03-13
    • 2010-09-13
    • 1970-01-01
    • 2011-03-14
    • 1970-01-01
    相关资源
    最近更新 更多