【问题标题】:Garbage collector vs. collections垃圾收集器与集合
【发布时间】:2015-09-11 07:58:33
【问题描述】:

我已经阅读了几篇关于 Java 垃圾收集的文章,但我仍然无法确定明确清除收集是否被认为是一种好习惯......因为我找不到明确的答案,所以我决定在这里问它.

考虑这个例子:

List<String> 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;

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

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

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

【问题讨论】:

    标签: java garbage-collection


    【解决方案1】:

    最后两行没有帮助。

    • 一旦list 变量超出范围*,如果这是对链接列表的最后一次引用,那么该列表就可以进行垃圾回收。立即将list 设置为null 不会增加任何价值。

    • 一旦列表符合垃圾回收条件,如果列表包含对它们的唯一引用,则对其元素进行处理。不需要清除列表。

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

    * 迂腐地说,控制垃圾回收的不是范围,而是可达性。可达性不是一句话就能概括的。请参阅 this Q&A 了解有关此区别的说明。


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

    假设我向按钮添加了一个监听器,如下所示:

    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);
    

    【讨论】:

    • 在您的答案中添加有关 Java 1.7 及更高版本中的转义分析的评论可能会很有用。我一般喜欢这个答案。谢谢。
    • @sam 这个补充说明了什么?我不知道 Java 1.7 及更高版本中的逃逸分析发生了什么。
    • 我没有太多具体的细节,所以我希望你能说点什么。 ;) 据我了解,在编译期间,可以确定对象具有方法调用的生命周期,并将在执行堆栈上分配。在这种情况下,更不需要清空和清除该对象,因为弹出堆栈将很快处理事情。谷歌出现了这个:docs.oracle.com/javase/7/docs/technotes/guides/vm/…
    • @sam 链表作为一种数据结构可能过于复杂,无法接受 EA。您不应该假设 EA 有效,因为它只是一种优化。您必须通过打开诊断输出来证明它在特定情况下确实有效。
    【解决方案2】:

    这取决于以下因素

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

    对于原始的、非分代的、跟踪垃圾收集器清除引用仅意味着额外的工作,而不会使 GC 上的事情变得更容易。但是,如果您不能保证及时取消对集合的所有引用,清除可能仍然有帮助。

    对于分代 GC,尤其是 G1GC,将集合(或引用数组)内的引用归零可能某些情况下通过减少跨区域引用来有所帮助。 p>

    但这只有在您确实拥有在不同区域创建对象并将它们放入另一个区域的集合中的分配模式时才会有所帮助。而且它还取决于 clear() 实现将这些引用清空,这将清除转换为 O(n) 操作,而它通常可以实现为 O(1) 一个。

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

    如果

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

    那么可能在释放集合之前调用clear()会有所帮助。

    因此,充其量这是一个非常特定于工作负载的微优化,仅应在实际条件下分析/监控应用程序并确定 GC 开销证明额外的清除成本是合理的之后应用。


    供参考,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<E> x = first; x != null; ) {
            Node<E> next = x.next;
            x.item = null;
            x.next = null;
            x.prev = null;
            x = next;
        }
        first = last = null;
        size = 0;
        modCount++;
    }
    

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

        在 Java 中,一个对象要么是活动的(可以通过某个其他对象拥有的引用访问),要么是死的(不能被任何其他对象的引用所有者访问)。只能从死对象中访问的对象也被认为是死对象,可以进行垃圾回收。

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

        因此,clear 方法除了擦除一个死对象对另一个死对象的引用之外没有任何作用。无论哪种方式,他们都会收集垃圾。

        【讨论】:

          猜你喜欢
          • 2011-01-06
          • 2017-11-03
          • 1970-01-01
          • 1970-01-01
          • 2010-10-04
          • 2018-12-30
          • 1970-01-01
          • 2011-11-07
          • 2013-04-01
          相关资源
          最近更新 更多