//像背書一樣,記錄下吧
托管堆上維護著一個指針NextObjPtr.該指針表示下一個新建對象在托管堆上的位置.C#的new Object會產生IL newobj指令,NextObjPtr傳遞給this,構造完會返回對象的地址.
在托管堆中 連續分配的對象可以保證它們的地址也是連續的,可以得到性能方面的一些提升.
在托管堆上分配的對象,都有兩個IntPtr的開銷字段,一個是對象類型指針,二是同步塊索引.在32位application中都是4Byte=32bit,在64位Application上是64bit=
垃圾收集器通過檢查在托管堆中是否有應用程序不再使用的對象來回收內存.垃圾收集器是如何知道一個對象是否是不再使用的呢?
*其實回收的不是我們不再使用的對象,而是那些在當前作用域不能訪問的對象.
應用程序的根:一個根即一個存儲位置,包含著指向引用類型的對象指針,->托管堆中的對象 or null(空值).其實就是那些在當前代碼/環境/context中可以訪問的對象引用.如static 字段,局部變量,方法參數,CPU寄存器等
那么GC怎么知道什么時候開始工作呢,來看對象的代
托管堆中開始是空的,新添加的對象,被稱為第0代,當應用程序占用內存很小時,沒有必要去動用GC垃圾回收,況且這個垃圾回收可能使性能變差,但這個很小是什么概念,就是我們對分配對象占用的內存要有一個閥值,小于這個值,我們不需要GC工作,例如選擇第0代對象256KB的預設空間,當分配的內存超出這個預設空間時,就會觸發GC來回收內存.
經過第一次垃圾回收,有些對象沒了,剩余存活的對象提升一個代,成為第一代,我們也為GC的第一代對象選擇一個閥值,例如2MB.這時還在不斷分配新對象,他們是第0代,當第0代的256KB滿了之后,回收它,將存活的并入第1代...
經過不斷回收,第1代的對象滿了之后,GC會回收第1代,存活的提升為第2代,CLR via C#書上說第二代取10MB,第二代滿了之后才會對第二代進行垃圾回收...
這樣利用代的概念,減少GC回收要掃描的內存空間,減少GC對第1代,第二代掃描的頻率.
變量在不可達時就會在下一次垃圾回收時被回收.
來看個例子
1 public static void Main()2 {3 var timer = new System.Threading.Timer(delegate {4 Console.WriteLine("timer elapsed at : " + DateTime.Now);5 GC.Collect();6 },null,0,1000);7 8 System.Console.ReadLine();9 }
這段代碼,在VS 中Debug配置下正常工作(即按照預期,回調一直執行),在Realse配置下只會打印一行,即GC.Collect()將timer回收了
在Debug中為什么就不被回收呢?
是VS生成的程序集方便調試,將所有變量的存活期撐到方法結束,VS給程序集打上System.Diagnostics.DebuggableAttribute表示是在調試,并且在參數上指定禁用JIT優化代碼
但是我們發布的時候可不能靠這個,我們得發布Realse版本,代碼會被JIT優化,在上面的代碼中,要保證對timer的引用,就不會被GC光顧~
修改代碼
1 public static void Main() 2 { 3 var timer = new System.Threading.Timer(delegate { 4 Console.WriteLine("timer elapsed at : " + DateTime.Now); 5 GC.Collect(); 6 },null,0,1000); 7 8 System.Console.ReadLine(); 9 timer.Dispose();//保持對timer的引用10 }
即可
Dispose函數,手動調用,釋放資源.
Finalize函數,在C#代碼中,類的析構函數會被編譯成終結函數(Finalize),供GC在垃圾回收該對象時自動調用
CriticalFinalizerObject類,在System.Runtime.ConstrainedExecution.CriticalFinalizerObject下面,該類沒有任何實現,繼承該類之后什么都不用做,會得到CLR and GC的特殊照顧.我們知道CLR在執行一個方法時JIT會將IL code編譯成Native code,是到第一次執行時才編譯,特么像解釋型語言,但是CriticalFinalizerObject類型(包括派生類型)的Finalize方法會被立即編譯,確保Finalize會被執行,本地資源會被釋放.
同時在Finalize執行順序上也有規則.CLR會先執行非CriticalFinalizerObject類的終結方法,后執行CriticalFinalizerObject類型的終結方法,因為這個特殊類型封裝的本地資源最后調用析構函數,可以保證之前的終結方法調用時本地資源不被關閉
在Winform項目里新建一個Form,打開Designer.cs文件,可以見到
Dispose(){ Dispose(true);}void Dispose(bool disposing){ if(disposing) { //blabla }}
見到這種Code,即是釋放模式:我們釋放資源有兩種情況,1是顯式調用Dispose方法,2是GC在判定對象是要被垃圾回收時,調用終結函數
在IDisposable.Dispose和終結函數的實現中,我們均可以用Dispose(bool disposing)來實現
區別在于disposing參數,它表示是否是在顯式調用Dispose()來釋放資源,如果是,我們在本類中所引用的其他對象,可以確定他們可以訪問,也就是還沒被GC回收,可以在if(disposing)里回收這些對象,而GC在回收的時候,我們不確定在本類中所引用的對象是否已經被GC回收.公共部分可以釋放一些GC無法處理的資源,如本地資源
1 var fs=new FileStream(path)2 var sw=new StreamWriter(fs);3 4 sw.Write(xxx)
StreamWriter在Write的時候,會寫到自己的緩沖區,在緩沖區滿的時候,或者Close的時候,寫到FileStream,同理FileStream緩沖區滿了之后寫到文件.MS沒有給StreamWriter實現終結方法,如果數據存在緩沖區,而且沒有close來關閉StreamWriter以Flush數據的話,數據就會丟失.
在關閉StreamWriter的時候,會關閉相關聯的FileStream,而不用顯式關閉
新聞熱點
疑難解答