簡體   English   中英

垃圾收集器與集合

[英]Garbage collector vs. collections

我已經閱讀了幾篇關於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;

從我在例如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或類似的多代收藏家
  • 在最終釋放之前緩慢累積對象(這通常將它們放在不同的區域,從而創建跨區域引用)
  • 您希望在清算時交換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++;
}

我不相信clear()會在這種情況下有所幫助。 一旦沒有對它們的引用,GC將刪除項目,因此理論上,僅設置list = null將具有相同的效果。 您無法控制何時調用GC,因此在我看來,除非您有特定的資源/性能要求,否則不值得擔心。 我個人仍然使用list = null;

如果要重用list變量,那么clear()當然是最好的選擇,而不是創建一個新的列表對象。

在Java中,對象是活動的(可通過某個其他對象擁有的引用訪問)或死亡(引用所有者無法通過任何其他對象訪問)。 只能從死對象訪問的對象也被視為已死並且有資格進行垃圾回收。

如果沒有活動對象具有對您的集合的引用,則它無法訪問並且有資格進行垃圾回收。 這也意味着你的所有集合的元素(以及它可能創建的任何其他輔助對象) 也是無法訪問的,除非其他一些活動對象具有對它們的引用。

因此,除了從一個死對象到另一個死對象的擦除之外,清除方法沒有任何效果。 他們將以任何方式收集垃圾。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM