1. 程式人生 > 實用技巧 >C#記憶體管理和垃圾回收機制

C#記憶體管理和垃圾回收機制

  • 資料型別
  • 垃圾回收機制

一、資料型別

C#中的資料型別分為值型別(Value type)引用型別(reference type),

值 類 型:所有的值型別都整合自 System.ValueType 上,除非加宣告?否則不可為null,儲存在堆疊(Stack,先進後出)上,常見的值型別有:整形、浮點型、bool、列舉等。

引用型別:所有的引用型別都繼承自System.Object 上,引用型別儲存在託管堆(Head,先進先出)上,常見的型別有:陣列、字串、介面、委託、object等。

拆箱和裝箱:引用型別和值型別的相互轉換叫做拆裝箱操作。

拆箱:拆箱就是將一個引用型物件轉換成任意值型!比如:

int i=0;
System.Object obj=i;
int j=(int)obj;

裝箱:裝箱就是隱式的將一個值型轉換為引用型物件。比如:

int i=0;
Syste.Object obj=i;

二、垃圾回收機制 GC

  1、簡介

C#中和Java一樣是一種系統自動回收釋放資源的語言,在C#環境中通過 GC(Garbage Collect)進行系統資源回收,在資料基本型別中介紹到,C#資料型別分為引用型別和值型別,

值型別儲存在Stack上,隨著函式的執行作用域執行完畢而自動出棧,所以這一型別的資源不是GC所關心 物件。GC垃圾回收主要是是指儲存在Heap上的資源。

.NET的GC機制有這樣兩個問題:
  首先,GC並不是能釋放所有的資源。它不能自動釋放非託管資源。


  第二,GC並不是實時性的,這將會造成系統性能上的瓶頸和不確定性。
  GC並不是實時性的,這會造成系統性能上的瓶頸和不確定性。所以有了IDisposable介面,IDisposable介面定義了Dispose方法,這個方法用來供程式設計師顯式呼叫以釋放非託管資源。使用using語句可以簡化資源管理。
  

  2、託管資源和非託管資源

上面介紹到,GC只釋放託管資源,那麼什麼是託管資源和費託管資源。

  託管資源:託管資源指的是.NET可以自動進行回收的資源,主要是指託管堆上分配的記憶體資源。託管資源的回收工作是不需要人工干預的,有.NET執行庫在合適呼叫垃圾回收器進行回收。

  非託管資源:非託管資源指的是.NET不知道如何回收的資源,最常見的一類非託管資源是包裝作業系統資源的物件,例如檔案,視窗,網路連線,資料庫連線,畫刷,圖示

等。這類資源,

垃圾回收器在清理的時候會呼叫Object.Finalize()方法。預設情況下,方法是空的,對於非託管物件,需要在此方法中編寫回收非託管資源的程式碼,以便垃圾回收器正確回收資源。

總結:託管資源是釋放由GC來完成,釋放的時間吧不一定,一般是系統感覺記憶體吃緊,會進行緊急回收資源。一個物件想成為被回收,首先需要成為垃圾,GC是通過判斷物件及其子物件有沒有指向有效的引用,

如果沒有GC就認為它是垃圾。垃圾回收機制通過一定的演算法得到哪些沒有被被引用、或者不再呼叫的資源,當這些垃圾達到一定的數量時,回啟動垃圾回收機制,GC回收實際上是呼叫了解構函式。

垃圾回收機制意味著你不需要擔心處理不再需要的物件了。咱們關心的主要是非託管資源的釋放。

垃圾回收時物件一共有三代 :0,1,2。每一代都有自己的記憶體預算,空間不足的時候會呼叫垃圾回收。為了提高效能都是按代回收,第0代超預算之後就回收第0代的物件,而存活下來的物件就提升為第1代,

依次類推,而往往經過多次0代的垃圾回收才能回收一次第1代。

GC進行垃圾回收是系統決定的,下面是進行強制回收的執行程式碼(非特殊情況下不要使用此方法,會影響系統效率,削弱垃圾回收器中優化引擎的作用,而垃圾回收器可以確定執行垃圾回收的最佳時間)

//對所有代進行垃圾回收。
GC.Collect();
//對指定的代進行垃圾回收。
GC.Collect(int generation); 
//強制在 System.GCCollectionMode 值所指定的時間對零代到指定代進行垃圾回收。
GC.Collect(int generation, GCCollectionMode mode); 

  3、非託管資源的釋放

在定義一個類時,可以使用兩種不同的機制類釋放非託管資源,這兩週機制有時候通常放在一起使用

1、宣告解構函式(終結器)嗎,作為類的成員

  建構函式可以在建立物件例項的時候執行某些操作,解構函式正好相反是資源建立以後被系統回收的時候執行的操作,垃圾回收器在回收物件之前會呼叫解構函式,所以在函式程式碼塊中可以寫釋放非

託管資源的程式碼。解構函式沒有返回值,沒有引數,沒有修飾符

    public class AA
        {
            ~AA()
            {
                //解構函式語法
            }
        }

解構函式會被編輯器翻譯成下面的程式碼:

       protected override void Finalize()
        {
            try
            {
                // Cleanup statements...     
            }
            finally
            {
                base.Finalize();
            }
        }

最終解構函式會被翻譯成上面的程式碼塊,重寫基類的Finalize()方法,然後最終呼叫 Base.Finalize()方法。

注意!大量的使用解構函式會影響效率!帶有解構函式的物件會被系統執行兩次才會被釋放掉。GC執行釋放資源時,沒有解構函式的資源會被直接釋放掉,假如目標物件有解構函式,會被先放進一個叫做“終結佇列”的

項中去,然後系統呼叫另一個高優先順序執行緒來執行 Finalize()方法,GC繼續回收其它物件。等方法執行完以後會將物件從終結佇列中清除出去,此時物件才是真正意義上的垃圾。等GC執行資源回收的時候,才回釋放掉終結佇列裡面的物件。

總結:

  • 託管堆中記憶體的釋放和解構函式的執行分別屬於兩個不同的執行緒。

  • 帶有解構函式的物件其生命週期會變長,由上知會進行兩次垃圾回收處理才能被釋放,如此一來將導致程式效能的下降。

  • 若一個物件引用了其他物件時,當此物件不能夠被釋放時,則其引用物件也就無法進行記憶體的釋放,也就意味著帶有解構函式的物件和其引用物件將從第0代提升到第一代,毫無疑問將影響程式的效能。

綜上所述,建議是不要實現其解構函式,這將大大降低程式的效能。

2、在類中實現 System.IDisposable 介面

實現IDisposable介面來顯示釋放系統資源

class Test:IDisposable  
    {

        #region IDisposable Support
        private bool disposedValue = false; // 要檢測冗餘呼叫

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: 釋放託管狀態(託管物件)。
                }

                // TODO: 釋放未託管的資源(未託管的物件)並在以下內容中替代終結器。
                // TODO: 將大型欄位設定為 null。

                disposedValue = true;
            }
        }

        // TODO: 僅當以上 Dispose(bool disposing) 擁有用於釋放未託管資源的程式碼時才替代終結器。
        ~Test()
        {
            // 請勿更改此程式碼。將清理程式碼放入以上 Dispose(bool disposing) 中。
            Dispose(false);
        }

        // 新增此程式碼以正確實現可處置模式。
        public void Dispose()
        {
            // 請勿更改此程式碼。將清理程式碼放入以上 Dispose(bool disposing) 中。
            Dispose(true);
            // TODO: 如果在以上內容中替代了終結器,則取消註釋以下行。
            // GC.SuppressFinalize(this); 
        }
        #endregion 
    }

① 當我們顯示呼叫Dispose()方法以後,會執行釋放費託管資源的操作,然後disposedValue會為Flase,所以我們多次呼叫也沒有關係。Dispose()呼叫執行完以後,執行GC.SuppressFinalize(this)(告訴GC不再執行終結器操作) 程式碼

② 如果我們不呼叫 Diapose()方法,系統會呼叫使用終結器操作,最後也是釋放非託管資源。

從例子可以看出,對於手動回收(disposing為true),除了非託管資源,還可以通知其他託管物件Dispose(),因為這時候內部的託管物件肯定沒回收。而到了自動回收,就不能通知其他託管物件了,因為垃圾回收可能已經把他們回收了, 而且垃圾回收會自動回收他們,也不用你通知了。

總結:

當釋放非託管資源時我們應該顯式的去實現Dipose()方法或者Close()方法,但是萬一我們忘記顯式去呼叫方法,此時還有一條退路,CLR會自動呼叫Finalize()方法,

很顯然呼叫Finalize()方法會大大降低程式的效能,沒關係,上述釋放模式關鍵的一點是通過手動釋放呼叫Dispose()方法可以阻止Finalize()方法的呼叫,換言之,上述通過手動釋放既釋放了非託管資源又加快了程式執行的速度,毫無疑問,這是一種完美的解決方案。

1、Finalize是系統決定執行的,我們無法干涉。Dispose是可以我們呼叫來釋放的。

2、Finalize只能釋放費託管資源,Dispose既可以釋放託管資源也可以釋放非託管資源。