在C#中,程序員無法直接在C#中刪除一個托管對象,因為C#不提供這個功能,那麼類的實例就需要通過CLR調用垃圾回收機制進行清除,回收內存。.NET垃圾回收器會壓縮空的內存塊來實現優化,為了輔助這一功能,托管堆會保存一個指針,它指向下一個對象將被分配的位置。那麼CLR是如何使用垃圾回收機制呢?首先,類實例化之後具體的對象會被分配到一塊叫托管堆的內存區域上,將托管堆中的對象的引用地址返回給函數中的引用變量,引用變量保存在棧內,要使用對象中的方法只需要使用點操作就可以。特別需要說一下的是,結構是數值類型,它直接分配在棧上(所以的數值類都是這樣的,只有引用類型才會保存在托管堆上)。實例化結束之後,垃圾回收器會在一個對象從代碼庫的任何部分都不可訪問的時候,將它從堆中刪除,例:
static void MakeCar()
{
Car mycar=new Car();
}
在例子中,Car的引用mycar直接在MakeCar中創建,並沒有被傳到該方法以外的作用域的,因此,在這個方法調用結束之後,這個對象就不會再被訪問,此時它就是垃圾回收器的回收目標,但是,要知道,一個對象在失去意義之後並不會立即被刪除,CLR調用垃圾回收器的標准是:在創建對象時,先判斷對象所需要的內存的大小,在判斷目前的托管堆是否有足夠的內存保存它,當托管堆沒有足夠的內存時,CLR就會調用垃圾回收器進行垃圾回收,因此,在對象失去意義之後還需要等待CLR調用垃圾回收器時才能被刪除。那麼CLR是如何判斷對象所需的內存,以及堆的內存是否夠用?當C#編譯器在遇到new關鍵字時,它會自動在方法的實現中加上一條CIL newobj 的指令,它會計算分配對象所需的總內存,檢查堆的內存空間,如果內存足夠,則調用類型的構造函數,最終將內存中的新變量的引用返回給調用者,它的地址是下一個對象指針的最後位置,若是內存不足,則CLR調用垃圾回收器釋放內存。在回調地址之後,就將對象的指針指向下一個可用的地址。那麼垃圾回收器如何判斷一個對象是否還在使用,這就要介紹一個應用程序根,所謂的根,說白了就是存儲堆上的對象的引用地址的存儲位置,在回收過程中運行庫會對對象進行判斷,判斷程序是否還可以訪問它們,也就是說它們的根是否還存在,若是不存在,則標記為垃圾,進而被清除對象,CLR壓縮內存,指針指向正確的位置。但是若是每一次進行垃圾回收的時候都要對托管堆上的所以數據進行一次判定,這種方法就太過於耗時耗力了,於是就有了代,代的設計思路是:對象在堆上存在的時間越長就越可能被保留。所以就將堆上的對象共分為0-2的3代,垃圾回收器在運行的時候,首先會檢測0代的對象,並且將那些不需要的對象釋放,空出內存,若是空出的內存不夠,則會往上面一級的1級代進行檢測,以此類推,直到獲取所需要的內存大小為止,而在這之後,0代上沒被刪除的對象就會被標記為1代,一代中未被刪除的對象就標記為2代,但是2代還是二代,因為這是上限。在這裡還得說一下,其實垃圾回收器使用的兩個堆,我們所說的是小對象堆,還有一個大對象堆,他存儲的是大於85k的對象,因為它的內容太大,對它進行修改的話,花費代價太大,所以在垃圾回收器極少會對它進行修改。
以上是垃圾回收器自動對對上面的數據進行回收,這並不需要人為的進行操控,但是這是對於托管在托管堆上面的對象,若是有些數據不是托管的資源呢?.NET提供了一個System.GC的類類型,它可以通過編程使用一些靜態成員與垃圾回收器進行交互,這種行為也叫作強制垃圾回收,它可以由我們自己決定什麼時候釋放某個對象的資源,而不用被動等待垃圾回收器運行,一般來說在不希望接下來的代碼被垃圾回收器打斷的運行時候(垃圾回收器的運行時間是不確定的),或者我需要一次性分配很多的對象的時候都會用到強制回收,強制回收使用GC.Collect()方法,在它的後面必須要調用GC.WaitForPendingFinalize(), 另外,使用Finalize()構建可終結對象,當應用程序的應用程序域從內存中卸載的話,CLR就會自動調用它的生命周期中所創建的每一個可終結對象的終結器進行強制回收,重寫Finalize()無法像普通的類一樣,它需要類似C++的析構語法,此外終結器還需在名稱之前加~,他不接受訪問修飾符,不接受參數,不支持重載,但是使用這種方式的話,需要兩次的來及回收才能真正的釋放該資源,並且由於是額外的處理,所以速度回變得非常慢。所以就可以考慮構建一個可處置對象,構建可處置對象需要實現IDisposable,這個方法不止可以釋放一個對象的非托管資源,而且還可以對任何它包含的可處置對象調用Dipose(),使用這種方法可以有自己調用釋放內存,若是忘記調用,也會有垃圾回收器進行釋放,所以這種方式在我看來會更安全好用一些。在C#類中還為實現了IDisposable的接口提供了一類語法:using,使用這種語法的好處在於,可以由Dispose()調用擴展try/catch結構,例如:using(class c=new class()){}在編譯之後,與class c=new class();try{}catch(){};是等同的。
在這一個章節內,我覺得還有一個非常使用的泛型類Lazy<>,但凡被這個類所定義的數據在代碼庫實際使用它之前是不會被創建的,這樣就可以使一些不常使用的大數據在實例化對象的時候不同時被創建,進而占用內存空間。例:
class song{
public string Artist{get;set;}
public string TrackName{get;set;}
public double TrackLength{get;set;}
}
class AllTracks
{
private Song[] allSongs=new Song[10000];
public AllTracks()
{
Console.WriteLine();
}
class MediaPlayer()
{
public void Play(){};
private AllTracks allsongs=new AllTracks();
public AllTracks GetAllTracks()
{
return allSongs;
}
}
}
main(){
MediaPlayer m=new MediaPlayer();//在這個時候,已經間接的創建10000個歌曲對象了
}
若是將MediaPlayer修改為:
class MediaPlayer()
{
public void Play(){};
private Lazy<AllTracks> allsongs=new Lazy<AllTracks>();
public AllTracks GetAllTracks()
{
return allSongs.Value;
}
}
調用方式就要改為:
main(){
MediaPlayer m=new MediaPlayer();//在這個時候,未創建10000個歌曲對象了
AllTracks songs=m.GetAllTracks();//此時,歌曲對象才創建
}