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 
    }
View Code

① 當我們顯示調用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既可以釋放托管資源也可以釋放非托管資源。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM