【问题标题】:How does memory allocation of an ArrayList work?ArrayList 的内存分配如何工作?
【发布时间】:2014-06-08 08:24:36
【问题描述】:

据我所知,当我们创建 ArrayList 时:

ArrayList<String> list = new ArrayList<String>(SIZE);

JVM 为其预留a contiguous part of memory。当我们向列表中添加新元素时,当元素数量达到SIZE 的 75% 时,它会保留一个新的、连续的内存部分并复制所有元素。

我们的名单越来越大。我们正在添加新对象,并且必须再次重建列表。

现在会发生什么?

JVM 正在寻找一段连续的内存,但没有找到足够的空间。

垃圾收集器可以尝试删除一些未使用的引用并对内存进行碎片整理。如果 JVM 在此过程之后无法为新的列表实例保留空间,会发生什么情况?

它是否创建一个新的,使用最大可能的段?哪个Exception会被抛出?

我读了这个问题Java: How ArrayList manages memory,答案之一是:

Reference doesn't consume much space. 但无论如何,都会占用一些空间。当数组变大时,这可能是一个问题。我们也不能忘记,我们还有其他使用内存空间的东西。

【问题讨论】:

  • ArrayList 在 75% 满时实际上不会调整大小;它会一直等到它完全装满,然后您尝试添加另一个元素。
  • 感谢您的评论。我检查了ArrayList 的实现。我从方法add(E e) 开始,我无法理解一种情况。在方法ensureCapacityInternal(int minCapacity) 中有这样的东西:if (minCapacity - elementData.length &gt; 0) {grow(minCapacity);}。它说什么?在这种情况下,minCapacity 是什么意思?方法grows 做Arrays.copyOf(elementData, newCapacity) 很清楚,但是为什么会有我之前提到的条件呢?
  • 如果您查看例如addAll(Collection c) 方法,您会看到它调用了ensureCapacity(size() + c.size())。所以minCapacity 是容纳我想要放入列表中的内容所需的最小容量。
  • @GirlyGirl Q1 - 哈希表实现通常使用素数作为表大小。 Q2 - 您不希望 HashMap 100% 满,因为这会降低访问项目的速度。

标签: java memory arraylist


【解决方案1】:

如果 JVM 无法分配请求的内存量,它会抛出

OutOfMemoryError

就是这样。实际上JVM内存分配只有两种可能的结果:

  1. 应用程序获得了请求的内存量。
  2. JVM 抛出 OutOfMemoryError。

没有中间选项,例如分配了一些内存。

与ArrayList无关,是JVM问题。如果您询问 ArrayList 是否以某种特殊方式管理这种情况 - 那么答案是“不,它没有”。它只是尝试分配它需要的内存量,并让 JVM 考虑其余部分。

【讨论】:

  • OutOfMemoryError 不会导致 JVM 退出。它只是java.lang.Error 的一个子类型。它将展开它引发的线程的堆栈,直到它被捕获。
  • @Dev 你是对的。但它之所以称为 Error 主要是因为没有太多理由去捕捉它——在 OutOfMemoryError 之后恢复应用程序几乎是不可能的。
  • 来自 Java 文档:“错误是 Throwable 的子类,表示合理的应用程序不应尝试捕获的严重问题。大多数此类错误是异常情况。”
  • 我同意将Errors 视为致命的最佳实践,但是应用程序可能会在退出之前尝试响应错误(日志记录、尽力清理)。此外,在多线程程序中,未捕获的错误可能不会导致 JVM 自行退出,它只会杀死引发它的线程。
  • @Dev 是的,感谢纠正我!调整了我的答案。
【解决方案2】:

在 Java 中,对对象的引用存储在连续的内存中。实际对象可以以非连续方式保持。因此,例如,您的数组可能有 10 个对象,JVM 只需要为对象引用保留内存,而不是对象。因此,如果每个引用占用一个字节(大约不是正确的值),但每个对象占用一个 KB,并且您有一个包含 10 个元素的数组,JVm 将尝试保留仅 1*10 B 即 10 B 的连续内存。对象可以驻留在 10 个不同的内存位置,总计 10KB。请记住,连续和非连续内存空间都用于分配给线程的内存。

当它需要调整数组大小时,JVM 试图找到一个更新长度的连续数组。因此,如果您想将数组的大小从 10 个元素调整为 20 个元素,它会尝试保留 20 KB 的连续空间(使用上面的示例)。如果它找到这个空间,它会将旧数组的引用复制到新数组。如果它没有找到这个空间,它会尝试做一个 GC 。如果仍然找不到空间,则会抛出 OutofMemoryException。

因此,在调整数组大小的任何时候,JVM 都需要找到一个连续的内存来存储新大小数组的引用。因此,如果您想将数组扩展为 1000 个元素的大小,并且每个引用都是一个字节,JVm 将尝试找到一个 1000* 1KB 的连续内存,即 1 MB。 如果找到该内存,它将复制引用,并为 GC 标记较旧的连续内存,每当 GC 下次运行时 如果找不到内存,会尝试GC,如果仍然找不到contiguos内存,则会抛出Out of memory异常

这是 ArrayList 中调整大小的代码。 http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/ArrayList.java#ArrayList.ensureCapacity%28int%29

【讨论】:

    【解决方案3】:

    一旦没有足够的堆空间来分配新数组,这将抛出 OutOfMemoryError。

    垃圾收集将始终在引发此错误之前完成。这将压缩内存并消除所有不再使用的较小尺寸的阵列。但是没有办法绕过这样一个事实,即旧数组、新数组和所有包含的对象都需要同时在内存中才能将旧内容复制到新列表中。

    因此,如果您的内存限制为 10 MB,并且数组占用 2 MB,并且正在将大小增加到 3 MB,并且字符串占用 6 MB,那么即使在此操作之后您只会抛出 OOM有 3 + 6 = 9 MB 的内存。避免这种情况的一种方法是,如果您想通过一个巨大的数组运行非常接近内存限制,则将数组的大小调整为开始时的完整大小,以便它永远不需要调整大小。

    【讨论】:

      【解决方案4】:

      我认为它会耗尽内存,因为在 JVM 可以扩展数组大小的情况下将没有空间可用。

      【讨论】:

        【解决方案5】:

        我要纠正的第一件事是,当我们向列表中添加新元素时,当元素数量达到 SIZE 的 100% 时,它会保留一个新的、连续的内存部分并复制所有元素。

        ArrayList 的新大小将是:

        ArrayList 的NewSize = (CurrentSize * 3/2) + 1

        但是不建议这样做,如果我们知道需要存储多少对象,那么我们可以使用以下 ArrayList 的构造函数:-

        ArrayList ar = new ArrayList(int initialCapacity);

        如果我们的 JVM 无法在堆上为 ArrayList 指定足够的连续空间,在运行时我们会得到 ​​p>

        • 运行时错误:OutOfMemoryError

        【讨论】:

        • 在我大约 90% 的工作中,我不知道 ArrayList 的大小,所以“从不推荐”是错误的,不知道你在哪里找到的 ;)
        猜你喜欢
        • 2022-01-17
        • 2018-01-29
        • 2018-04-21
        • 2021-04-07
        • 1970-01-01
        • 2013-01-07
        • 2015-12-05
        • 1970-01-01
        相关资源
        最近更新 更多