【问题标题】:Java collections: What happens when "size" exceeds "int"?Java 集合:当“size”超过“int”时会发生什么?
【发布时间】:2010-08-23 13:11:59
【问题描述】:

我知道 Java 集合非常需要内存,我自己做了一个测试,证明 4GB 勉强足以将几百万个 Integers 存储到 HashSet 中。

但是如果我有“足够”的记忆呢? Collection.size() 会发生什么?

编辑:已解决:Collection.size() 在超出整数范围时返回 Integer.MAX
新问题:如何确定那么集合的元素呢?

注意 1:抱歉,这可能是一个让我用谷歌搜索你的问题,但我真的什么也没找到 ;)

注意 2:据我了解,集合的每个整数条目是: reference + cached_hashcode + boxed_integer_object + real_int_value,对吧?

注意 3:有趣的是,即使使用 JDK7 和“压缩指针”,当 JVM 使用 2GB 的实际内存时,它在 VisualVM 中仅显示 1.5GB 分配的内存。

对于那些关心的人:

测试来源:

import java.util.*;
import java.lang.management.*;

public final class _BoxedValuesInSetMemoryConsumption {
  private final static int MILLION = 1000 * 1000;

  public static void main(String... args) {
    Set<Integer> set = new HashSet<Integer>();

    for (int i = 1;; ++i) {
      if ((i % MILLION) == 0) {
        int milsOfEntries = (i / MILLION);
        long mbytes = ManagementFactory.getMemoryMXBean().
            getHeapMemoryUsage().getUsed() / MILLION;
        int ratio = (int) mbytes / milsOfEntries;
        System.out.println(milsOfEntries + " mil, " + mbytes + " MB used, "
            + " ratio of bytes per entry: " + ratio);
      }

      set.add(i);
    }
  }
}

执行参数:

在 OpenSuse 11.3 x64 下使用 x64 版本的 JDK7 build 105 进行测试。

-XX:+UseCompressedOops -Xmx2048m

输出结果:

1 mil, 56 MB used,  ratio of bytes per entry: 56
2 mil, 113 MB used,  ratio of bytes per entry: 56
3 mil, 161 MB used,  ratio of bytes per entry: 53
4 mil, 225 MB used,  ratio of bytes per entry: 56
5 mil, 274 MB used,  ratio of bytes per entry: 54
6 mil, 322 MB used,  ratio of bytes per entry: 53
7 mil, 403 MB used,  ratio of bytes per entry: 57
8 mil, 452 MB used,  ratio of bytes per entry: 56
9 mil, 499 MB used,  ratio of bytes per entry: 55
10 mil, 548 MB used,  ratio of bytes per entry: 54
11 mil, 596 MB used,  ratio of bytes per entry: 54
12 mil, 644 MB used,  ratio of bytes per entry: 53
13 mil, 827 MB used,  ratio of bytes per entry: 63
14 mil, 874 MB used,  ratio of bytes per entry: 62
15 mil, 855 MB used,  ratio of bytes per entry: 57
16 mil, 902 MB used,  ratio of bytes per entry: 56
17 mil, 951 MB used,  ratio of bytes per entry: 55
18 mil, 999 MB used,  ratio of bytes per entry: 55
19 mil, 1047 MB used,  ratio of bytes per entry: 55
20 mil, 1096 MB used,  ratio of bytes per entry: 54
21 mil, 1143 MB used,  ratio of bytes per entry: 54
22 mil, 1191 MB used,  ratio of bytes per entry: 54
23 mil, 1239 MB used,  ratio of bytes per entry: 53
24 mil, 1288 MB used,  ratio of bytes per entry: 53
25 mil, 1337 MB used,  ratio of bytes per entry: 53
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

最终使用了大约 2 GiB 的实际内存,而不是显示的 1.3 GiB,因此每个条目的消耗甚至大于 53 个字节。 p>

【问题讨论】:

  • 我相信你会想要改变你“自己做了一个测试,证明 4GB 勉强足以将几百万个整数存储到一个 HashSet”的断言——我的生产服务会下降几秒钟内就结束了,这几乎是真的。
  • @unhillbilly: 你认为我在撒谎吗? ;) 编辑了我的问题:粘贴了我的测试代码和我的结果。您可以自己测试,然后告诉我们您的结果和测试环境。
  • 现在真正的问题是什么 - 1) 当大小大于 Integer.MAX 时,int size() 会返回什么,或者 2) 为什么不能在 Set 中存储超过 2500 万个整数?
  • @matt b:您没有正确阅读:1) size() 在元素数量为 Integer.MAX 或更大时返回 Integer.MAX。 2)您可以存储更多元素,这只是我的测试,因为它超出了给定的内存限制而终止。在我的问题中,我提出了计算每个条目所需的内存量的建议。测试证明了这一点:每个整数 53 个字节,而“本机”大小为 4 个字节。
  • @unhillbilly: 1) 错字:4G/56 ~= 71M,不是 17M。 2)对不起,我不明白,你能解释一下吗?什么花了什么?

标签: java memory collections integer overflow


【解决方案1】:

我知道 Java 集合非常 内存不足,自己做了一个测试, 证明 4GB 勉强够用 将几百万个Integers 存储到一个 HashSet.

Java 堆!= 系统内存。 Java 的默认堆大小仅为 128MB。请注意,这也与 JVM 使用的内存不同。

关于您的问题:来自文档,

public int size()

返回 this 中的元素个数 收藏。如果这个集合 包含超过Integer.MAX_VALUE 元素,返回Integer.MAX_VALUE

【讨论】:

  • 很好,我应该先查看 javadoc,典型的错误。谢谢!
  • 现在,我改变了问题(如何确定元素的实际数量),所以我不能接受它作为正确答案。抱歉,我应该提出一个新问题,但是现在,有了这些 cmets,我认为现在为时已晚。
  • 另一种查找方法行为的方法是下载源代码(您可能已经拥有它,在您的 sdk 目录中查找 src.zip)并直接查看代码。如果你使用的是 eclipse,你甚至可以告诉 eclipse 在哪里找到这个文件,这样你就可以按住 Ctrl 键点击所有库方法的源代码。
【解决方案2】:

您的问题似乎与标题的内容完全不同。

您已经回答了标题中的问题(返回Integer.MAX_VALUE)。不:您无法通过普通 API 安全地迭代集合和计数来找出“真实”大小(当然使用 long)。

如果您想存储int 中的Set 值,并且您知道值的范围可能会变得非常大,那么BitSet 实际上可能是一个更好的实现:

import java.util.*;
import java.lang.management.*;

public final class IntegersInBitSetMemoryConsumption {
  private final static int MILLION = 1000 * 1000;

  public static void main(String... args) {
    BitSet set = new BitSet(Integer.MAX_VALUE);

    for (int i = 1;; ++i) {
      if ((i % MILLION) == 0) {
        int milsOfEntries = (i / MILLION);
        long mbytes = ManagementFactory.getMemoryMXBean().
            getHeapMemoryUsage().getUsed() / MILLION;
        double ratio = mbytes / milsOfEntries;
        System.out.println(milsOfEntries + " mil, " + mbytes + " MiB used, "
            + " ratio of bytes per entry: " + ratio);
      }

      set.set(i);
    }
  }
}

这将产生一个固定大小的数据结构,可以在不改变大小的情况下保存范围内的所有值,并且占用相对较少的内存(每个可能值 1 位加上一些开销)。

然而,这种方法有两个缺点:

  • 不支持负的int
  • 它不提供Set API

通过编写使用两个BitSet 对象(可能延迟分配)分别保存正值和负值范围并为Set 接口实现适配器方法的包装器,可以轻松解决这两个问题。

【讨论】:

  • 感谢您的提示!但是,实际上,我使用SetIntegers 只是为了演示开销和内存增长比预期快的“问题”。
  • @java:在这种情况下,解决方案很简单:当原始值足够时不要使用包装器。
  • 好的,如果除了计数没有其他方法,这也是一个答案;)——我想知道在 java 中是否存在针对 Collections.sizeLong(Collection&lt;?&gt; col) 之类的 RFE 错误...
  • @java:在少数情况下,您的集合确实比这更大,您可能无论如何都不想循环遍历它,并且知道确切的大小实际上并不那么重要。事实上,我认为最好的解决方案是一个额外的 HugeCollection 接口,该大小的集合可以实现。
【解决方案3】:

来自源代码:

 /**
 * Returns the number of elements in this collection.  If this collection
 * contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
 * <tt>Integer.MAX_VALUE</tt>.
 * 
 * @return the number of elements in this collection
 */
int size();

【讨论】:

    【解决方案4】:

    任何真正的处理器架构的通用答案是你不能。原因很简单:分配的对象(至少 1 个字大小)不能超过可寻址内存。

    当然,鉴于 JVM 的虚拟特性,这种情况可能会发生。 int 将始终是 32 位签名的,您可以在 64 位机器上实现和运行 JVM,其中可以处理超过 2GB 的内存。

    在这种情况下,文档告诉我们 Integer.MAX_INT 将被返回...这是一个大问题,因为任何使用依赖于 i &lt; col.size() 停止的整数变量的循环都将永远运行(尽管我认为任何循环 2**31-1 次的东西都需要足够长的时间让你无论如何都想终止该进程)。

    【讨论】:

      猜你喜欢
      • 2012-04-24
      • 2018-10-25
      • 1970-01-01
      • 2022-08-03
      • 1970-01-01
      • 2010-12-08
      • 2016-02-20
      • 2020-06-15
      • 1970-01-01
      相关资源
      最近更新 更多