43

清空集合(在我的情况下是一个 ArrayList)与创建新集合(并让垃圾收集器清除旧集合)的优缺点是什么。

具体来说,我有一个ArrayList<Rectangle>list. 当某种情况发生时,我需要清空list并用其他内容重新填充它。我应该打电话list.clear()还是只做一个新的ArrayList<Rectangle>,让旧的被垃圾收集?每种方法的优缺点是什么?

4

4 回答 4

51

回收一个ArrayList(例如通过调用clear)的优点是可以避免分配一个新的开销,以及增加它的成本......如果你没有提供一个好的initialCapacity提示。

回收的缺点ArrayList包括:

  • clear()方法必须分配给s 后备数组null中的每个(已使用)插槽。ArrayList

  • clear()不会调整后备数组的大小以释放内存。因此,如果您反复填充和清除列表,它将最终(永久)使用足够的内存来表示它遇到的最大列表。换句话说,您增加了内存占用。您可以通过调用来解决这个问题trimToSize(),但这会创建一个垃圾对象,等等1

  • 存在可能影响性能的地方性和跨代问题。当您反复回收 一个ArrayList时,该对象及其后备数组可能会被永久使用。这意味着:

    • 列表对象和表示列表元素的对象可能位于堆的不同区域,可能会增加 TLB 未命中和页面流量,尤其是在 GC 时。

    • 将(年轻一代)引用分配到(终身)列表的后备数组中可能会产生写屏障开销……取决于 GC 实现。

不可能准确地模拟现实应用程序的性能权衡。变量太多了。然而,“公认的智慧”是,如果你有足够的内存2和一个半体面的垃圾收集器,回收通常不是一个好主意。

还值得注意的是,现代 JVM 可以非常有效地分配对象。它只需要更新堆的“空闲”指针并写入 2 或 3 个对象头字。内存归零是由 GC 完成的……除此之外,这样做的工作大致相当于将clear()正在回收的列表中的引用归零的工作。


1 - 创建一个新的 ArrayList 比调用 clear() 后跟 trimToSize(...) 更好。使用后者,您将获得垃圾收集开销和多余归零的开销。

2 - 如果垃圾对象与非垃圾对象的比例很高,则复制收集器效率更高。如果分析这种收集器的工作方式,成本几乎都发生在查找和复制可达对象上。对垃圾对象唯一需要做的就是将撤离的“来自”空间的块零写入准备好分配新对象。


我的建议是不要回收ArrayList对象,除非你有明显的需要最小化(垃圾)对象的创建率;例如,因为它是减少(有害)GC 暂停的唯一选择。

在所有条件相同的情况下,在现代 Hotspot JVM 上,我的理解是通过执行以下操作可以获得最佳性能:

  • 分配新的 ArrayList 对象而不是回收。
  • initialSize分配列表对象时使用准确的提示。稍微高估总比低估好。
于 2013-08-22T04:40:37.340 回答
41

您保留容器并clear在您想减少 GC 负载时调用:clear()将数组内的所有引用归零,但不会使数组符合垃圾收集器回收的条件。这可能会加快未来的插入,因为里面的数组ArrayList不需要增长。当您计划添加到容器的数据与您清除的数据大小大致相同时,这种方法特别有利。

此外,您可能需要clear在其他对象持有对您将要清除的数组的引用时使用。

当新数据的大小可能与以前不同时,释放容器并创建一个新容器是有意义的。当然你可以通过 clear()结合调用来达到类似的效果trimToSize()

于 2013-08-22T02:42:38.400 回答
1

由于已经写了有趣的点,您可以更深入地考虑它。

阅读有关中断模式的文章后,我还没有意识到这一点,请参阅LMAX 的中断模式如何工作?

不仅可以重用底层集合,还可以重用集合中的实体

例如,假设生产者和消费者用例。生产者可以一遍又一遍地将数据填充到相同的(循环)数组中,甚至使用相同的实体。只需清除属性、内部状态并填充它自己的。

从 GC 的角度来看,这是一个更好的解决方案。但这显然是特殊情况,并非对每个问题都有用。

于 2013-08-22T13:21:46.977 回答
-1

真的没关系...

List.clear() 实现将内部数组的引用设置为 null。如果没有更多引用,则有效地将对象设置为垃圾回收。

如果您唯一关心的是内存,那么这两种方法都没有真正可衡量的差异。即使在操作方面,差异也将在于数组分配(在调整大小操作中)和其他此类操作。

但是清除它可能会稍微好一些,尽管如果创建一个新列表更具可读性,我会这样做。

于 2013-08-22T02:45:36.587 回答