2014-12-05 69 views
8

我自己和一位同事在.NET中垃圾收集對象時有不同的看法。看看下面的代碼:.NET服務器垃圾收集和對象生命週期

Stream stream=getStream(); 
using(var request=new Request(stream)) 
{ 
    Stream copy=request.Stream; 

    // From here on can "request" be garbage collected? 

    DoStuff1(); 
    DoStuff2(copy); 
} 

我的同事聲稱,在發佈版本中使用服務器垃圾收集器,它是有效的request對象是調用request.Stream後垃圾回收運行。他斷言這隻會發生在服務器垃圾收集器上,而不會發生在工作站垃圾收集器上。

之所以這樣,是因爲Request類有一個終結器正在關閉給予請求的Stream。因此,當DoStuff2去使用流時,它得到了「對象處置」異常。由於終結器只能由垃圾收集器運行,所以我的同事說垃圾收集必須發生在finally塊的末尾之前,但是在最後一次使用後request

但是,我相信自從上述代碼只是速記是這樣的:

Stream stream=getStream(); 
Request request=null; 

try 
{ 
    Stream copy=request.Stream; 

    // From here on can "request" be garbage collected? 

    DoStuff1(); 
    DoStuff2(copy); 
} 
finally 
{ 
    if(request!=null) 
     request.Dispose(); 
} 

然後request不能被垃圾調用request.Stream後收集它仍然是來自finally塊到達。

另外,如果垃圾回收器有可能收集對象,則finally塊可能會顯示未定義的行爲,因爲Dispose將在GC對象上調用,這是沒有意義的。同樣,也無法優化掉finally塊作爲異常可以在try/using塊任何垃圾回收已經發生之前,這將需要finally塊執行中被拋出。

忽略終結器中關閉流的問題是否有可能垃圾收集器在finally塊的末尾之前收集對象,實際上優化finally塊中的邏輯?

+2

你是對的,直到finally運行,請求才能被GCed。 – 2014-12-05 12:55:17

+1

請注意,如果request.Dispose沒有使用實例字段,請求*可以在對其調用Dispose之前收集。這是一種罕見的情況。 – usr 2014-12-05 12:58:06

+0

建議:不要使用終結器。然後,這個問題就變得沒有意義了。您無法在工作中檢測到​​GC(除非通過非常不尋常的方式,例如終結器或調試API)。爲什麼Request.Finalize需要關閉流?流本身有一個終結器。 – usr 2014-12-05 12:59:11

回答

10

這個問題有很多,所以我會先解決總體問題。

  1. using聲明中聲明的變量不會被垃圾塊結束之前收集的正是你所指示的原因 - 一個參考,以便調用Dispose()在隱finally塊舉行。

  2. 如果您發現自己在C#中編寫終結器,那麼您可能是在做錯某些事情。如果你在C#中的終結器調用Stream.Dispose(),你肯定是做錯了什麼。在.NET本身的實現之外,我看到了數百個被濫用的終結器,以及實際需要的exactly 1 finalizer。有關更多信息,請參閱DG Update: Dispose, Finalization, and Resource Management

  3. ObjectDisposedException與最終確定無關。當代碼在對象上調用Dispose()(不是finalize)時,通常會發生此異常,然後稍後調用該對象執行某些操作。

    有時代碼處置Stream時並不明顯。一個讓我感到意外的例子是使用StreamContent作爲使用HttpClient發送HTTP請求的一部分。實現在發送請求後調用Stream.Dispose(),所以我必須編寫一個包裝StreamDelegatingStream,以便在從HttpWebRequestHttpClient轉換期間保留我們庫的原始行爲。

一種情況,你可以看到一個ObjectDisposedException是,如果getStream()方法將緩存Stream實例,並返回其多個未來的呼叫。如果Request.Dispose()處置該流,或者如果DoStuff2(Stream)處置該流,則下次您嘗試使用該流時,將獲得ObjectDisposedException

+0

絕對爲真。 Object Disposal是框架實現的一種模式實現,它允許輕鬆地等待對象釋放資源並且不直接綁定到GC。唯一的限制是Disposed對象通常釋放資源,並且此資源可用於刪除。在Stream中,您需要檢查每種情況下的實現以查看發生了什麼。 – Miguel 2014-12-12 04:33:26

7

根據language specification的第3.9節「如果對象或其任何部分無法通過任何可能的繼續執行來訪問,除了運行析構函數之外,該對象被認爲不再使用,並且它有資格銷燬。「 using介紹了一個finallyDispose調用(第8.13節),這是其他東西比運行析構函數,並且除了在情況必須時執行甚至finally塊不執行(該規範並沒有涵蓋,而且通常只發生無論如何,當你的整個AppDomain到達墳墓場時)。

using塊一個目的是不符合GC。在你的榜樣,request不符合銷燬條件,直到using語句之後,不論它是在塊的其餘部分使用。唯一的角落案例(如其他人的評論中所述)是當您的Dispose實施不使用其參數this時。在這種情況下,Dispose不會阻止對象是符合銷燬條件 - 但如果這種情況發生,當可以收集它的問題是沒有實際意義(因爲它應該是 - 垃圾收集,正確的實施,應該沒有對正確實施的計劃有可觀察的影響)。

+1

我認爲using語句創建了一個弱引用,但是在語言規範中沒有看到這種效果。檢查變量是否可以GC'ed的方法是調用GC.ForceGC *兩次*。第一次調用將導致Dispose方法被調用。第二次調用將導致釋放對象內存 – 2014-12-10 21:30:59

+0

@ KC-NH:沒有'GC.ForceGC'方法。通過實現一個終結器,設置一個外部標誌並調用'GC.Collect()',然後調用'GC.WaitForPendingFinalizers()',可以人爲地檢測一個對象是否有資格被破壞。顯然這不是你想要在生產代碼中做的事情,所以它只有教學價值。如果你在'using'中用一個變量來嘗試這個,你會發現直到'using'完成才能收集它。在我的測試中,即使'Dispose()根本不訪問任何成員,情況也是如此。 – 2014-12-10 22:31:15

+0

對不起。我的意思是GC.Collect。我會通過讓Dispose()記錄它被調用來測試它。如果你有一個終結器,記錄它也被調用。 – 2014-12-10 22:34:04