垃圾收集器与集合

我已经阅读了几篇关于Java中垃圾收集的post,但我还是无法决定是否明确地清除一个集合被认为是一种好的做法……而且由于我找不到明确的答案,我决定在这里问一下。

考虑这个例子:

List list = new LinkedList(); // here we use the list, perhaps adding hundreds of items in it... // ...and now the work is done, the list is not needed anymore list.clear(); list = null; 

从我在例如LinkedListHashSet实现中看到的, clear()方法基本上只循环给定集合中的所有项,将其所有元素(在LinkedList情况下也引用next和previous元素)设置为null

如果我做对了,将list设置为null只是从list删除一个引用 – 考虑到它是唯一的引用,垃圾收集器最终会处理它。 我只是不知道在这种情况下,在垃圾收集器处理列表的元素之前还需要多长时间。

所以我的问题是 – 上面列出的示例代码的最后两行实际上是否有助于垃圾收集器更有效地工作(即更早地收集列表的元素)或者我只是让我的应用程序忙于“无关的任务”?

最后两行没有帮助。

  • 一旦list变量超出范围* ,如果这是对链表的最后一次引用,那么该列表就有资格进行垃圾收集。 事先将list设置为null不会增加任何值。

  • 一旦列表符合垃圾收集条件,那么如果列表仅包含对它们的引用,则执行其元素。 清除列表是不必要的。

在大多数情况下,您可以信任垃圾收集器来完成其工作,而不需要“帮助”它。

*迂腐地说,它不是控制垃圾收集的范围 ,而是可达性用一句话来总结可达性并不容易。 有关此区别的说明,请参阅此问答 。


此规则的一个常见例外是,如果您的代码保留的引用时间超过了所需的时间。 这个典型的例子是听众。 如果向某个组件添加侦听器,稍后不再需要该侦听器,则需要显式删除它。 如果不这样做,那个侦听器可以禁止自身和它引用的对象的垃圾收集。

假设我在一个按钮中添加了一个监听器:

 button.addListener(event -> label.setText("clicked!")); 

然后在标签上删除,但按钮仍然存在。

 window.removeChild(label); 

这是一个问题,因为按钮具有对侦听器的引用,并且侦听器具有对标签的引用。 即使标签在屏幕上不再可见,标签也不能被垃圾收集。

现在是采取行动并获得GC好的一面的时候了。 我添加它时需要记住听众…

 Listener listener = event -> label.setText("clicked!"); button.addListener(listener); 

…这样我就可以在完成标签后删除它:

 window.removeChild(label); button.removeListener(listener); 

这取决于以下因素

  • 如何实现clear()
  • 集合持有的条目的分配模式
  • 垃圾收集器
  • 是否可能有其他东西保留其集合或子视图(不适用于您的示例,但在现实世界中很常见)

对于原始的,非代的,跟踪垃圾收集器清除引用仅意味着额外的工作,而不会使GC更容易。 但是,如果您无法保证及时清除所有对该集合的引用,则清算仍然有用。

对于分代GC,尤其是G1GC,在某些情况下,通过减少跨区域引用,在集合(或引用数组)中对引用进行归零可能会有所帮助。

但是,只有当您实际拥有在不同区域中创建对象的分配模式并将它们放入另一个区域中的集合时,这才有用。 并且它还取决于clear()实现使这些引用无效,当它通常被实现为O(1)时,它将清除转换为O(n)操作。

因此,对于您的具体示例,答案如下:

如果

  • 你的名单很长寿
  • 在该代码路径上创建的列表构成/保留应用程序生成的大部分垃圾
  • 你正在使用G1或类似的多代collections家
  • 在最终释放之前缓慢累积对象(这通常将它们放在不同的区域,从而创建跨区域引用)
  • 您希望在清算时交换CPU时间以减少GC工作负载
  • clear()实现是O(n)而不是O(1),即空出所有条目。 OpenJDK的1.8 LinkedList这样做的。

那么在释放集合本身之前调用clear() 可能是有益的。

因此,这是一个特定于工作负载的微优化,只应在实际条件下分析/监控应用程序并确定GC开销certificate清算的额外成本是合理的。


供参考,OpenJDK 1.8的LinkedList::clear

 /** * Removes all of the elements from this list. * The list will be empty after this call returns. */ public void clear() { // Clearing all of the links between nodes is "unnecessary", but: // - helps a generational GC if the discarded nodes inhabit // more than one generation // - is sure to free memory even if there is a reachable Iterator for (Node x = first; x != null; ) { Node next = x.next; x.item = null; x.next = null; x.prev = null; x = next; } first = last = null; size = 0; modCount++; } 

在这种情况下,我不相信clear()会有所帮助。 一旦没有对它们的引用,GC将删除项目,因此理论上,仅设置list = null将具有相同的效果。 您无法控制何时调用GC,因此在我看来,除非您有特定的资源/性能要求,否则不值得担心。 我个人仍然使用list = null;

如果要重用列表变量,那么当然clear()是最好的选择,而不是创建新的列表对象。

在Java中,对象是活动的(可通过某个其他对象拥有的引用访问)或死亡(引用所有者无法通过任何其他对象访问)。 只能从死对象访问的对象也被认为已死,并且有资格进行垃圾回收。

如果没有活动对象具有对您的集合的引用,则它无法访问并且有资格进行垃圾回收。 这也意味着你的所有集合的元素(以及它可能创建的任何其他辅助对象) 也是无法访问的,除非其他一些活动对象具有对它们的引用。

因此,除了从一个死对象到另一个死对象的擦除之外,清除方法没有任何效果。 他们将以任何方式收集垃圾。