程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 類(2)- 解構函數和垃圾收集,解構垃圾收集

類(2)- 解構函數和垃圾收集,解構垃圾收集

編輯:C#入門知識

類(2)- 解構函數和垃圾收集,解構垃圾收集


定義了一個類後,我們就可以new任何數量的對象。此時,將產生托管堆和棧上的內存分配,堆上將開辟一塊新的空間負責儲存類對象,而棧上僅僅儲存引用。一般來說,垃圾收集和內存管理僅僅是相對於托管堆而言的。而c#的內存管理非常方便-就是根本不用管理,垃圾收集器將負責所有的工作。對於垃圾收集,有幾個問題需要明確。

什麼是垃圾:通常來說,某個對象如果在代碼的任何部分都不可訪問的時候,就將其視為垃圾。

垃圾收集器何時工作:當它認為需要的時候(下文詳解)或者用戶呼叫。

垃圾收集器不會理會非托管資源,那麼怎麼辦:自己寫代碼釋放,有finalize和dispose兩種方法

基本概念

托管堆保存著一個指針,其指示著下一個對象將被分配的位置,當系統運行到new關鍵字時,將會在CIL中加入newobj指令,其負責下面的工作:

1. 計算要分配的新對象所需要的總內存數,包括所有自身成員和基類

2. 檢查托管堆上的指針,判斷托管堆是否還有足夠的內存分配該對象,如果足夠則開始執行靜態和普通構造函數,然後返回引用(指向下一個對象的指針的上一個位置)

3. 移動托管堆指針到下一個可用的位置

4. 如果第二步發現空間不夠,則執行一次垃圾回收

注意,將對象設為null並不會引起垃圾回收,或者清除托管堆上的空間。這僅僅是將對象和托管堆兩者的聯系(引用)移除,即對象不指向托管堆上的任何東西。

 

基本算法

當進行垃圾回收時,運行庫將檢查托管堆中的對象,判斷應用程序是否仍然可以訪問他們。整個垃圾回收分為兩步。

階段1: Mark-Sweep 標記清除階段,先假設heap中所有對象都可以回收,然後找出不能回收的對象,給這些對象打上標記,最後heap中沒有打標記的對象都是可以被回收的

垃圾回收器在執行回收時通過檢查應用程序的根來確定不再使用的對象。簡單的說,根就是一個存儲位置,保存著對托管堆上一個對象的引用(就是上圖中的那兩個箭頭)。每個應用程序都有一組根,它們被儲存在一個列表中,每個根或者引用托管堆中的對象,或者設置為null。

垃圾回收器可以訪問活動根的列表,對照此列表檢查應用程序的根,並在此過程中創建一個圖表,在其中包含所有可從這些根中訪問的對象。不在該圖表中的對象將無法從應用程序的根中訪問。垃圾回收器會考慮將無法訪問的對象標記為垃圾,並釋放為它們分配的內存。在下圖中,由於將c2設置為Null,沒有任何根指向托管堆中的c2,於是其將被視為垃圾。

階段2: Compact 壓縮階段,標記完所有垃圾之後,就釋放它們的內存空間,內存空間變得不連續,在堆中移動這些對象,使他們重新從堆基地址開始連續排列,類似於磁盤空間的碎片整理。

alt

 

垃圾收集算法的改善 - 分代

當進行垃圾收集時,掃描所有對象將是非常費時間的,為了優化這個過程,出現了分代算法。分代算法的精髓就是“對象存在的時間越長則可能越重要,越有可能應該保留”。

.NET將堆分成3個代齡區域: Gen 0、Gen 1、Gen 2;

alt

 

所有的對象在創建時都放在第0代。如果Gen 0 heap內存達到阈值,則觸發0代GC,(此時僅僅會掃描0代的對象)0代GC後Gen 0中幸存的對象進入Gen1。如果Gen 1的內存達到閥值,則進行1代GC,1代GC將Gen 0 heap和Gen 1 heap一起進行回收,幸存的對象進入Gen2。2代GC將Gen 0 heap、Gen 1 heap和Gen 2 heap一起回收,Gen 0和Gen 1比較小,這兩個代齡加起來總是保持在16M左右;Gen2的大小由應用程序確定,可能達到幾G,因此0代和1代GC的成本非常低,2代GC稱為full GC,通常成本很高。粗略的計算0代和1代GC應當能在幾毫秒到幾十毫秒之間完成,Gen 2 heap比較大時,full GC可能需要花費幾秒時間。大致上來講.NET應用運行期間,2代、1代和0代GC的頻率應當大致為1:10:100。

 有了分代算法,程序就不需要掃描所有對象了。

(http://www.cnblogs.com/springyangwc/archive/2011/06/13/2080149.html)

其他

垃圾回收通常不需要人工干預,但也有少數時候是例外。因為垃圾回收只適用於托管對象(更准確的說是只適用於可終結的對象),對於非托管對象,則是CLR不能控制或者管理的部分,這些資源有很多,比如文件流,數據庫的連接,系統的窗口句柄,打印機資源等等……這些資源一般情況下不存在於Heap(內存中用於存儲對象實例的地方)中。不享受垃圾回收,我們仍然要自己去釋放內存。此時我們有很多選擇。例如我們將要預見有數個較大的資源將要創建,而系統可能已經沒有那麼多資源在托管堆上,那麼我們可以做的事情有:

1. 強制調用垃圾回收,清除托管堆上所有的沒用的托管對象

2. 使用dispose()或對任何對象實現IDisposable接口,使對象變為可處置的,然後顯式或隱式調用dispose(),精准的干掉這個對象

3. 對非托管對象實現finalize()方法(只能通過定義一個解構函數才能實現),使對象變為可終結的,然後該對象就可以享受垃圾回收器的自動清除好處

其中2和3可以合並,而這也是標准做法。

強制垃圾回收

強制垃圾回收需要調用GC.Collect方法,其可以強制進行一次垃圾回收。這個方法有重載版本,可以指定回收的代。在調用完該方法之後,可以馬上調用WaitForPendingFinalizers(),可以確定在程序繼續執行之前,所有可終結的對象都必須執行所有必要的清除工作。

重寫Finalize方法

其實我們沒辦法override Finalize方法,只能通過定義一個解構函數隱式的做到這件事,這是因為,當編譯器執行解構函數時,將自行在被隱式重寫的finalize方法中增加許多代碼。我們可以在解構函數中加入代碼清除非托管資源,甚至還可以打印東西到控制台上,不過,解構函數的執行時間是無法預測的(在垃圾回收時)。對非托管對象定義一個解構函數之後,其就變成可終結的了,然後我們就可以期待系統以處置托管對象相同的方法處置它。不過,這個方法較為被動(仍然要等待垃圾回收),如果要主動的清除,則要實現IDisposable接口,使對象變為可處置的,然後顯式或隱式調用dispose()

解構函數(終結器)和dispose()

(http://www.cnblogs.com/luminji/archive/2011/03/29/1997812.html)

如果我們的類型使用到了非托管資源,或者需要顯式釋放的托管資源,那麼,最好讓類型繼承接口IDisposable。這相當於是告訴調用者,該類型是需要顯式釋放資源的,你需要調用我的Dispose方法。調用dispose和垃圾收集毫無關系,垃圾收集也不會自動調用dispose()。所以,如果用戶記得調用,則不需要再調用解構函數,而如果用戶忘了調用,需要解構函數善後(正式的方法就是這樣做的)。一般來說,用using塊包住可以令系統自行調用dispose(當離開using塊之後)。

正式的處置方法

微軟定義了一個正式的對釋放資源的處置模式,其注意到以下幾個問題:

1. 可以多次調用dispose()而不出問題(如果已經調用過,再次調用不應該觸發清理過程)

2. 如果調用了dispose(),則不需要再次調用終結器

3. 對象既是可終結的也是可主動釋放內存的

class BaseClass : IDisposable
    {
        bool disposed = false;

        //供外部調用
        public void Dispose()
        {
            //進行垃圾回收
            Dispose(true);
            //通知垃圾回收不再調用終結器,因為我們已手動釋放了內存
            GC.SuppressFinalize(this);
        }

        //內部
        protected virtual void Dispose(bool disposing)
        {

            if (disposed)
                return;

            if (disposing)
            {
                //清理托管資源
            }

            //清理非托管資源

            //讓類型知道自己已經被釋放
            disposed = true;
        }

        /// <summary>
        /// 必須,以備程序員忘記了顯式調用Dispose方法
        /// </summary>
        ~BaseClass()
        {
            //必須為false
            Dispose(false);
        }
    }

其中解構函數是一種特殊的函數,其和類的名稱相同,前加一波浪號~。提供解構函數的全部意義在於不能奢望類型的調用者肯定會主動調用Dispose方法,基於解構函數會被垃圾回收器調用這個特點,終結器被用做資源釋放的補救措施。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved