GC關鍵方法解析

來源:互聯網
上載者:User

第二節.GC關鍵方法解析

  1.Dispose()方法

  Dispose可用於釋放所有資源,包括託管的和非託管的,需要自己實現。

  大多數的非託管資源都要求手動釋放,我們應當為釋放非託管資源公開一個方法,實現釋放非託管資源的方法有很多種,實現IDispose介面的Dispose方法是最好的,這可以給使用你類庫的程式員以明確的說明,讓他們知道怎樣釋放你的資源;而且C#中用到的using語句快,也是在離開語句塊時自動調用Dispose方法。

  這裡需要注意的是,如果基類實現了IDispose介面,那麼它的衍生類別也必須實現自己的IDispose,並在其Dispose方法中調用基類中Dispose方法。只有這樣的才能保證當你使用衍生類別執行個體後,釋放資源時,連同基類中的非託管資源一起釋放掉。

  插曲:使用using與try+finally的區別

  可以說2者沒有任何區別,因為using只是編輯器級的最佳化,它與try+finally有著相同的作用,以下是一段使用using的代碼,它在IL階段也是以try+finally呈現的:

  C#:

  MSIL:

  但是,using的優點是,在代碼離開using塊時,using會自動調用Idispose介面的Dispose()方法。

Code
public partial class _Default : System.Web.UI.Page
{   
protected void Page_Load(object sender, EventArgs e)
  {
using (DataSet ds = new DataSet())
    {
  }
  }
}

Code
.method family hidebysig instance void  Page_Load(object sender,
class [mscorlib]System.EventArgs e) cil managed
  {
// 代碼大小       29 (0x1d)
    .maxstack  2
    .locals init ([0] class [System.Data]System.Data.DataSet ds,
             [1] bool CS$4$0000)
    IL_0000:  nop
    IL_0001:  newobj     instance void [System.Data]System.Data.DataSet::.ctor()
    IL_0006:  stloc.0
    .try
    {
      IL_0007:  nop
      IL_0008:  nop
      IL_0009:  leave.s    IL_001b
    }  // end .try
finally
    {
      IL_000b:  ldloc.0
      IL_000c:  ldnull
      IL_000d:  ceq
      IL_000f:  stloc.1
      IL_0010:  ldloc.1
      IL_0011:  brtrue.s   IL_001a
      IL_0013:  ldloc.0
      IL_0014:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
      IL_0019:  nop
      IL_001a:  endfinally
    }  // end handler
    IL_001b:  nop
    IL_001c:  ret
  } // end of method _Default::Page_Load

  2. GC.Collect()方法

  如果我們在程式中顯式的調用了垃圾收集器的collect介面,那麼垃圾收集器會立即運行,完成記憶體對象的標記、壓縮與清除工作,使用GC.Collect(i)還可以指定回收的代,然而aicken並不贊成各位同學顯式調用它:

  ⑴. GC.Collect()做的並不只是回收記憶體,就像第一節中介紹的,在回收了記憶體之後,GC會重新整理記憶體,修正對象指標,讓空閑記憶體連續,供CLR順序分配記憶體,提高建立對象的效率。記憶體壓縮整理工作非常耗用計算資源。

  ⑵.很少有人會關心到GC除了在記憶體吃緊以及資源空閑時運行,還會在什麼時候運行。 其實GC的運行時機,還要受到一個叫做“策略引擎”的組件控制,它會觀察GC的收集頻率、效率等等。它會根據GC回收效果,調整GC啟動並執行頻率:即當某次GC回收效果頗豐時,它便會增加GC啟動並執行頻率,反之亦然。

  所以如果剛剛發生了一次自然的收集,垃圾對象就會非常之少,而此時程式又顯式的進行了收集調用,那麼自然, GC雖然小有收穫,但是策略引擎就會認為:這很不值得,才收集了這麼點垃圾,也許該減少GC的次數。這樣一來,垃圾收集器努力保持的自然節奏就被打亂了。

  同時,物件類型的建立效率與頻率,也會被“策略引擎”捕捉到,從而改變代的數量與容量。

  所以,額外的調用GC,代價高昂,甚至會降低效率。顯示的調用GC.Collect(),實質是在用“時間換空間”,而通常在程式設計中,我們推薦的設計原則是“空間換時間”,比如使用各種各樣的緩衝。

  也有例外,如果你掌握了整個應用程式的情況,明確的知道何時會產生大量垃圾,也是可以顯示調用該方法的。

  綜上,盡量不要顯示調用GC.Collect(),因為伺服器的CPU比記憶體要貴的多!

  3. 解構函式(Finalize())

  我們知道,GC只負責釋放託管資源,非託管資源GC是無法釋放的。類似檔案操作、資料庫連接等都會產用非託管資源。

Finalize方法是用於釋放非託管資源的,等同於C#中是解構函式,C#編譯器在編譯建構函式時,會隱式的將解構函式編譯為Finalize()對應的代碼,並確定在finally塊中執行了base.Finalize()。

  解構函式中只能釋放非託管資源,而不要在任何託管資源進行析構,原因如下:

  ⑴你無法預測解構函式的運行時機,它不是按順序執行的。當解構函式被執行的時候,也許你進行操作的託管資源已經被釋放了。

  ⑵包含Finalize()的對象,需要GC的兩次處理才能刪除。

  ⑶CLR會在單獨的線程上執行所有對象的Finalize()方法,無疑,如果頻繁的Finalize(),會降低系統的效能。

  下面我們來重點說說第⑵點,為何包含Finalize()的對象,需要兩次GC才能被清除。

  首先要瞭解與Finalize相關的兩個隊列:終止隊列(Finalization Queue)與可達隊列(Freachable Queue),這兩個佇列儲存體了一組指向對象的指標。

  當程式中在託管堆上分配空間時(new),如果該類含有解構函式,GC將在Finalization Queue中添加一個指向該對象的指標。

  在GC首次運行時,會在已經被確認為垃圾的對象中遍曆,如果某個垃圾對象的指標被Finalization Queue包含,GC將這個對象從垃圾中分離出來,將它的指標儲存到Freachable Queue中,並在Finalization Queue刪除這個對象的指標記錄,這時該對象就不是垃圾了——這個過程被稱為是對象的複生(Resurrection)。當Freachable Queue一旦被添加了指標之後,它就會去執行對象的Finalize()方法,清除對象佔用的資源。

  當GC再次運行時,便會再次發現這個含有Finalize()方法的垃圾對象,但此時它在Finalization Queue中已經沒有記錄了(GC首次運行時刪掉了它的Finalization Queue記錄),那麼這個對象就會被回收了。

  至此,通過GC兩次運行,終於回收了帶有解構函式的對象。

  複活執行個體:

Code
private void Form1_Load(object sender, EventArgs e)
{
  Resource re = new Resource();  
  re = null;GC.Collect();
  GC.WaitForPendingFinalizers();
//首次GC.Collect()沒起作用哦。
  label1.Text = re.num.ToString();

public class Resource
{
public int num;
~Resource()
  {
    。。。
  }

  看了上面的代碼,大家應該瞭解什麼是複活了吧!那麼為什麼要複生呢?因為首次GC時,這個對象的Finalize()方法還沒有被執行,如果不經過複生就被GC掉,那麼就連它的Finalize()一起回收了,Finalize()就無法運行了,所以必須先複生,以執行它的Finalize(),然後再回收。

  還有兩個方法ReRegisterForFinalize和SuppressFinalize需要講一講,ReRegisterForFinalize是將指向對象的指標重新添加到Finalization Queue中(即召喚系統執行Finalize()方法),SuppressFinalize是將對象的指標從Finalization Queue中移除(即拒絕系統執行Finalize()方法)。

  SuppressFinalize用於那些即有解構函式來釋放資源,又實現了Dispose()方法釋放資源的情況下:將GC.SuppressFinalize(this)添加至Dispose()方法中,以確保程式員調用Dispose()後,GC就不必再次收集了,例如以下代碼:

  即實現Idisposable中的Dispose()方法,又使用解構函式,一個雙保險,大家不要迷惑,其實在釋放非託管資源時,使用一個即可,推薦使用前者。

Code
public class Resource : Idisposable
{
private bool isDispose = false;
//實現Dispose(),後面還有解構函式,以防程式員忘記調用Dispose()方法
public void Dispose()
      {
       Dispose(true);
     GC.SuppressFinalize(this);
      }
protected virtual void Dispose(bool disposing)
   {
if (!isDispose)
    {
if (disposing)
     {
//清理託管資源
     }
//清理非管資源
    }
    isDispose = true;
   }
~ Resource ()
   {
    Dispose(false);
   }
 }

  4.弱引用(WeakReference)

  最後一個話題:弱引用。在編程中,對於那些大對象建議使用這種引用方式,這種引用不影響GC回收:我們用過了某個對象,然後將其至null,這樣GC就可以快速回收它了,但是沒過多久我們又需要這個對象了,沒辦法,只好重新建立執行個體,這樣就浪費了建立執行個體所需的計算資源;而如果不至null,就會浪費記憶體資源。對於這種情況,我們可以建立一個這個大對象的弱引用,這樣在記憶體不夠時GC可以快速回收,而在沒有被GC回收前我們還可以再次利用該對象。

Code
public class SomeObject
{
  。。。
}
public static void Main()
{
  SomeObject so = new SomeObject();
  WeakReference WRso = new WeakReference(so);
   so = null;
  Console.WriteLine(WRso.IsAlive); // True
// 調用GC 手動回收。
  GC.Collect();
  Console.WriteLine(WRso.IsAlive); // False
}

  看到沒,在so = null;後,它的弱引用依然是可用的。所以對於大對象的使用,aicken建議使用此種方式。另外,弱引用有長短之分:長弱引用在對象終結後,依然追蹤對象;短弱引用則反之,aicken不建議人為幹預GC的工作成果,所以推薦使用短弱引用,即上面代碼中的方式。

通過以上的講解,相信大家已經能夠很全面的瞭解.Net GC方面的知識了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.