有時咱們在運行程序時,會出現Debug版本和Release版本運行結果不一致的狀況。現給出C#中的一個例子,這個例子是Jeffrey在他的《CLR via C#》中"垃圾回收"這一章給出的,頗有意思。
其實你們也會猜想,Debug版和Release版編譯後的代碼確定會有不同的地方,是的!是會這樣的。要否則也不會出現運行結果不一致,呵呵!閒話不說咱們來看看代碼,代碼很簡單。(調試環境WinXP + VS2003 + C#)
class Program { [STAThread] static void Main(string[] args) { TimerCallback tm = new TimerCallback(ShowTime); Timer t = new Timer(tm,null,0,2000);
Console.ReadLine(); //t.Dispose(); (1) } private static void ShowTime(Object o) { Console.WriteLine(DateTime.Now.ToString()); GC.Collect(); //強制GC (2) } } html |
你能夠在Debug版本中運行一下,會發現該報時程序工做正常(一直會報時)。而在Release版本中時間報只顯示一下。
爲何呢?這是由於咱們在(2)處強制了一次GC(Garbage Collect)即垃圾回收。在Release版中當咱們程序運行到GC.Collect()時,Timer類型對象t是能夠被GC的。但Debug版本中t是做爲一個本地參數而不能被GC的。
咱們再分解一下整個過程(僅列出關鍵過程):
1,建立Timer類型對象t;
2,t定時調用委託tm;
3,tm方法執行;
4,tm方法進行Garbage Collect;
5,待讀取一行字符;(其間t定時器重複執行步驟2)
6,退出;
能夠看出第4,5步是個關鍵,若是咱們一直等待輸入就能不停打印。但問題是:在Debug版中確實是這樣的,在Release版中卻只打印了一次....How mysterious!
TOM: 這時t是否是不工做了?
JERRY: 對的,若是是Release版本中,至第5步時t確實不工做了,由於:它已經不存在了,被GC了.
TOM: 不....不....不,你慢一點,別忽悠我^_^!!!,那Debug版爲何它就不被GC呢?
JERRY: 這是因它,它在Debug版中不符合GC的條件.
TOM: 你鼻子變長了....
JERRY: 不信? 你看...這,這,這,諾..還有這.
爲了讓TOM相信這是真的,JERRY用ILDasm分別打開了Debug版本和Release版本的程序並雙擊打開Main方法.
------------
Debug版:
-------------------------------------------------
.........省略了一些行........
// 代碼大小 32 (0x20)
.maxstack 5
.locals ([0] class [mscorlib]System.Threading.Timer t)
IL_0000: ldnull
IL_0001: ldftn void Class1.Program::ShowTime(object)
IL_0007: newobj instance void [mscorlib]
System.Threading.TimerCallback::.ctor(object,native int)
IL_000c: ldnull
IL_000d: ldc.i4.0
IL_000e: ldc.i4 0x7d0
IL_0013: newobj instance void [mscorlib]System.Threading.Timer::.ctor
(class[mscorlibSystem.Threading.TimerCallback,
object,int32,int32)
IL_0018: stloc.0
IL_0019: call string [mscorlib]System.Console::ReadLine()
IL_001e: pop
IL_001f: ret
//仍可以使用t對象
} // end of method Program::Main
-------------------------------------------------
在Debug版中咱們定義的t是做爲一個本地參數(locals[0]),在
IL_0018位置,
它參考到新new的Timer對象.這時須要注意一點是的,即便在IL_001f處,仍經過locals[0]引用到t對象.
因此在託管堆(Managed Heap)中,該對象仍象是可達對象(Reachable object).也就不符合被回收的條件。因此只要Main函數不結束,t就不能被回收。
---------------
Release版:
-------------------------------------------------
.........省略了一些行........
// 代碼大小 32 (0x20)
.maxstack 5
IL_0000: ldnull
IL_0001: ldftn void Class1.Program::ShowTime(object)
IL_0007: newobj instance void [mscorlib]
System.Threading.TimerCallback::.ctor(object,native int)
IL_000c: ldnull
IL_000d: ldc.i4.0
IL_000e: ldc.i4 0x7d0
IL_0013: newobj instance void [mscorlib]System.Threading.Timer::.ctor
(class[mscorlibSystem.Threading.TimerCallback,
object,int32,int32)
IL_0018: pop
IL_0019: call string [mscorlib]System.Console::ReadLine()
IL_001e: pop
IL_001f: ret //至此,也沒有辦法能夠refer to至t對象,t能夠被Garbage Collect
} // end of method Program::Main
-------------------------------------------------
而在Rlease版中位置:
IL_0018只用了pop方法彈出該對象(也就是t對象).這至關於丟棄了t對象,這時t被標記爲可回收。
但有一點須要瞭解的是:t雖被丟棄,還仍存活在託管堆(Managed Heap)中,直到t調用ShowTime,而在ShowTime函數中調用GC爲止。當ShowTime中調用GC時,因爲t被標記爲可回收,因此t對象被回收。整個過程看起來是這個樣子的:
正是因爲在Release版本中t是做爲臨時變量,用完後被GC強制回收,因此t只能工做一次,便經過調用GC結束了本身的生命,可憐的娃!
爲了使Debug版和Release運行結果保持一致,你能夠有兩個選擇:
1,在Console.ReadLine只後調用t.Dispose(),即取消我最早給出的代碼的(1)處的註釋。由於t對象在建立以後還要被引用,因此建立的t對象也被做爲一個本地參數來保存,生成的IL以下:
.locals init (class [mscorlib]System.Threading.Timer V_0)
2,註釋掉我最早給出的代碼的(2)處的GC.Collect函數的調用.這樣雖然在Release版中t對象已被標識爲可回收,但些時沒有內存需求(這只是一個假設),CLR並不會回收t對象.
顯然方法2是不可取的,咱們只把但願寄託在CLR不進行Garbage Collect,但在現實編程中這是不現實的。在t被觸發的間隔間誰也不能保證CLR不進行Garbage Collect.若使用方法2我稍改了一下代碼,Release版本的程序又不能工做了。
class Program { [STAThread] static void Main(string[] args) { Timer t = new Timer(new TimerCallback(ShowTime),null,0,500); System.Threading.Thread.Sleep(1000); //新增 Thread gc = new Thread(new ThreadStart(GcCollect));//新增 gc.Start(); //新增 Console.ReadLine(); } private static void ShowTime(Object o) { Console.WriteLine(DateTime.Now.ToString()); } private static void GcCollect() //新增 { GC.Collect(); } } 編程 |
這段代碼中,我雖沒有在ShowTime中進行GC,但另外一個線程的CoCollect函數卻調用了GC.Collect,這會迫使CLR進行Garbage Collect。t只是存活了一小會兒,仍舊被回收了。
但若使用方法1,上面這段代碼在Release版本中仍能正常工做。
後記,Garbage Colletion是一個彷佛很神祕的東西。由於時常咱們並不知道何時CLR進行Carbage Collect.之前經常將.net程序性能很差的緣由都"嫁禍"在GC的頭上,其實這是片面的。真正的緣由是咱們不瞭解GC,不瞭解GC的工做原理。