2011-05-20 123 views
28

我在C#中有一個計時器,它在其方法中執行一些代碼。在代碼中我使用了幾個臨時對象。在C中釋放內存的正確方法是什麼?#

  1. 如果我有像Foo o = new Foo();的方法中,這是否意味着每個計時器滴答聲的時候,我正在創建一個新的對象和新的引用該對象?

  2. 如果我有string foo = null然後我只是把時間放在foo裏,是否和上面一樣?

  3. 垃圾回收器是否永遠刪除對象,引用或對象是否不斷創建並保留在內存中?

  4. 如果我只是聲明Foo o;而不是指向任何實例,那麼在方法結束時是不是放置了?

  5. 如果我想確保一切都被刪除,什麼是做的最好的方式:通過在年底

  6. 調用Dispose方法

    • 與方法
    • 內using語句通過將Foo o;放在定時器的方法之外,並將其分配o = new Foo()在裏面,那麼在方法結束後刪除指向該對象的指針,垃圾收集器將刪除該對象。

回答

29

1.如果我有類似Foo o = new Foo();裏面的方法,是否 的意思是每次計時器滴答, 我正在創建一個新的對象和一個新的 引用該對象?

是的。

2.如果我有字符串foo = null,然後我只是把時間放在foo中, 是否與上面相同?

如果你問的行爲是否相同,那麼是的。

3.Does垃圾收集永遠刪除對象和基準或 對象的持續創建和 留在記憶?

這些對象所使用的內存在引用被視爲未被使用後肯定會被收集。

4.如果我只是聲明Foo o;而不是指向任何實例,當方法結束時不是 ?

不,因爲沒有對象被創建,所以沒有收集對象(處置不是正確的詞)。

5。如果我想確保一切都被刪除,什麼是 做的最好的方式,

如果對象的類實現IDisposable那麼你一定要儘快撥打貪婪Disposeusing關鍵字使得這個更容易,因爲它以自動異常安全的方式調用Dispose

除此之外,除了停止使用該對象之外,實際上沒有其他事情需要做。如果引用是局部變量,則當它超出範圍時,它將有資格收集。 如果它是一個類級別的變量,那麼您可能需要分配null才能使其符合條件。


這在技術上是不正確的(或者至少有點誤導)。一個對象在超出範圍之前可能有資格收集。 CLR經過優化,可在檢測到不再使用引用時收集內存。在極端情況下,即使其中一個方法仍在執行,CLR也可以收集一個對象!

更新:

這裏是證明了GC將收集的對象,即使他們可能仍然在範圍內的例子。您必須編譯Release版本並在調試器之外運行。

static void Main(string[] args) 
{ 
    Console.WriteLine("Before allocation"); 
    var bo = new BigObject(); 
    Console.WriteLine("After allocation"); 
    bo.SomeMethod(); 
    Console.ReadLine(); 
    // The object is technically in-scope here which means it must still be rooted. 
} 

private class BigObject 
{ 
    private byte[] LotsOfMemory = new byte[Int32.MaxValue/4]; 

    public BigObject() 
    { 
     Console.WriteLine("BigObject()"); 
    } 

    ~BigObject() 
    { 
     Console.WriteLine("~BigObject()"); 
    } 

    public void SomeMethod() 
    { 
     Console.WriteLine("Begin SomeMethod"); 
     GC.Collect(); 
     GC.WaitForPendingFinalizers(); 
     Console.WriteLine("End SomeMethod"); 
    } 
} 

在我的機器,而SomeMethod仍在執行終結運行!

+1

如果沒有該根對象的引用,對象的方法會如何? – Yaur 2011-05-20 06:06:44

+0

@Yaur:好問題:考慮一個不使用其他實例成員(變量或方法)的實例方法。這意味着只需要提取對象引用來傳遞'this'引用。在此之後,只要CLR可以檢測到該對象即使它仍然可以被植根,它也不會在以後使用,在技術上符合條件。 – 2011-05-20 13:12:44

+0

@Brian Gideon我明白你的觀點,但我對此表示懷疑,因爲CLR使用mark-and-sweep來確定GC是否符合這種情況,或者該對象甚至在技術上符合垃圾回收的「合格條件」。但無論如何,這是一個足夠有趣的案例來做一些測試。 – Yaur 2011-05-20 13:34:52

15

.NET垃圾回收器爲您處理所有這些事情。

它能夠確定何時不再引用對象,並將(最終)釋放已分配給它們的內存。

+0

您確定連計時器內創建的對象都被刪除嗎?因爲我遇到了一個問題,那就是我的應用程序在10分鐘內是700MB,而且我猜測應該在計時器中刪除的對象出現問題。 – user579674 2011-05-20 00:16:06

+0

除非你正在做一些事情來保留那些在定時器的「打勾」方法範圍內創建的引用,否則它們將在方法退出後的某個時間*被釋放。這是垃圾收集器運行時檢測到沒有任何東西仍然指向這些對象。如果你的計時器在足夠快的週期內進行滴答,我可以看到如何消耗大量的內存。 – Yuck 2011-05-20 00:19:54

+3

它處理所有這些......除非它沒有。不用擔心內存管理是一個失控堆增長的好方法。 – 2011-05-20 00:23:42

5

對象一旦他們 超出範圍 成爲無法訪問(謝謝本!),可以進行垃圾回收。除非垃圾收集器認爲你的內存不足,否則內存不會被釋放。

對於託管資源,垃圾收集器將知道這是什麼時候,並且您不需要執行任何操作。

對於非託管資源(例如到數據庫或打開文件的連接),垃圾收集器無法知道它們消耗了多少內存,這就是爲什麼您需要手動釋放它們(使用dispose或更好使用塊)

如果對象沒有被釋放,或者你有足夠的內存並且沒有必要,或者你在應用程序中維護對它們的引用,因此垃圾收集器不會釋放它們(如果你真的使用這個參考你保留)

+2

s /超出範圍/無法訪問/。範圍實際上只與(未捕獲的)值類型的局部變量有關。 – 2011-05-20 00:22:33

+0

關於您提到的打開的文件,磁盤上的文件大小與文件在內存中的內存量相同(假定它未被更改)。 – user579674 2011-05-20 00:29:06

+0

除非您將整個文件讀入內存。當你打開一個文件時,操作系統可以用不同的方式鎖定它(讀/寫等)。垃圾收集是非確定性的(即它在需要時發生),所以如果文件句柄沒有明確釋放,文件可能會長時間保持鎖定狀態。文件句柄也是有限的,垃圾收集器不知道任何這些,這就是爲什麼文件應該關閉,而不是留給垃圾收集器。 – 2011-05-20 00:36:07

2
  1. 是的
  2. 你是什麼意思?每次運行該方法時都會重新執行。
  3. 是的.Net垃圾回收器使用一種算法,該算法以任何全局/範圍內的變量開始,在它跟隨它遞歸查找的任何引用後遍歷它們,並刪除內存中任何被認爲無法訪問的對象。see here for more detail on Garbage Collection
  4. 是的,方法中聲明的所有變量的內存在方法退出時釋放,因爲它們都是不可訪問的。此外,任何已聲明但從未使用的變量都將由編譯器進行優化,因此實際上您的變量永遠不會佔用內存。
  5. using聲明只是簡單地在對象退出時調用IDisposable對象,所以這相當於您的第二個項目符號點。兩者都會表明您已完成對象並告訴GC您已準備好放棄它。覆蓋對象的唯一引用將會產生類似的效果。
+1

CrazyJugglerDrummer提供的鏈接是一個非常棒的GC閱讀。 – 2011-05-20 00:32:45

1

垃圾收集器將來到並清理不再引用它的任何東西。除非在Foo中有非託管資源,否則調用Dispose或在其上使用using語句不會真的有幫助。

我很確定這適用,因爲它仍然在C#中。但是,我使用XNA開發了一個遊戲設計課程,我們花了一些時間討論C#的垃圾收集器。垃圾收集很昂貴,因爲您必須檢查是否有任何對要收集的對象的引用。所以,GC試圖儘可能延長這一點。所以,只要你沒有耗盡物理內存,當你的程序達到700MB時,它可能就是GC懶惰,不擔心它。

但是,如果您只是在循環外部使用Foo o,並且每次都創建o = new Foo(),則它應該都可以正常工作。

2

讓我們一一回答你的問題。

  1. 是的,你讓每當執行該語句一個新的對象,但是,它會「超出範圍」當您退出方法,它是符合垃圾回收。
  2. 那麼除了你使用了一個字符串類型之外,這和#1是一樣的。字符串類型是不可變的,每當你做任務時你會得到一個新對象。
  3. 是垃圾收集器收集超出範圍對象,除非您將該對象分配給具有大範圍的變量,例如類變量。
  4. 是的。
  5. using語句只適用於實現IDisposable接口的對象。如果是這種情況,通過一切手段最適合方法範圍內的對象。除非你有足夠的理由這麼做,否則不要把Foo o放在更大的範圍內。最好將任何變量的範圍限制在有意義的最小範圍內。
2

下面是一個簡單的概述:

  • 一旦引用都消失了,你的對象將可能被垃圾收集。
  • 你只能依靠統計收集來保證你的堆大小正常,只要所有引用垃圾的地方都消失了。換句話說,不能保證一個特定的對象會被垃圾收集。
    • 因此,您的終結器也永遠不會被保證被調用。避免終結者。泄漏的
  • 兩種常見來源:
    • 事件處理程序和代表們引用。如果您訂閱了一個對象的事件,那麼您正在引用它。如果您有一個委託給對象的方法,則引用它。
    • 非託管資源按定義不會自動收集。這是IDisposable模式的用途。
  • 最後,如果您想要一個不妨礙對象被收集的引用,請查看WeakReference。

最後一件事:如果你聲明Foo foo;沒有分配它,你不必擔心 - 沒有泄漏。如果Foo是引用類型,則不會創建任何內容。如果Foo是一個值類型,它將被分配到堆棧上,因此會自動清理。

1

正如Brian指出的,GC可以收集任何無法訪問的東西,包括仍在範圍內的對象,甚至這些對象的實例方法仍在執行。考慮下面的代碼:

class foo 
{ 
    static int liveFooInstances; 

    public foo() 
    { 
     Interlocked.Increment(ref foo.liveFooInstances); 
    } 

    public void TestMethod() 
    { 
     Console.WriteLine("entering method"); 
     while (Interlocked.CompareExchange(ref foo.liveFooInstances, 1, 1) == 1) 
     { 
      Console.WriteLine("running GC.Collect"); 
      GC.Collect(); 
      GC.WaitForPendingFinalizers(); 
     } 
     Console.WriteLine("exiting method"); 
    } 

    ~foo() 
    { 
     Console.WriteLine("in ~foo"); 
     Interlocked.Decrement(ref foo.liveFooInstances); 
    } 

} 

class Program 
{ 

    static void Main(string[] args) 
    { 
     foo aFoo = new foo(); 
     aFoo.TestMethod(); 
     //Console.WriteLine(aFoo.ToString()); // if this line is uncommented TestMethod will never return 
    } 
} 

如果與調試版本運行,使用調試器附着,或與指定行註釋掉TestMethod的永遠不會返回。但是沒有調試器的情況下運行TestMethod將會返回。