引用類型賦值為null與加速垃圾回收
在標準的Dispose模式中,提到了需要及時釋放資源,卻并沒有進一步細說讓引用等于null是否有必要。
有一些人認為等于null可以幫助垃圾回收機制早點發現并標識對象是垃圾。其他人則認為這沒有任何幫助。是否賦值為null的問題首先在方法的內部被人提起。現在,為了更好的闡述提出的問題,我們來撰寫一個Winform窗體應用程序。如下:
{
Method1();
Method2();
}
private void button2_Click(object sender, EventArgs e)
{
GC.Collect();
}
private void Method1()
{
SimpleClass s = new SimpleClass("method1");
s = null;
//其它無關工作代碼(這條注釋源于回應回復的朋友的質疑)
}
private void Method2()
{
SimpleClass s = new SimpleClass("method2");
}
}
class SimpleClass
{
string m_text;
public SimpleClass(string text)
{
m_text = text;
}
~SimpleClass()
{
MessageBox.Show(string.Format("SimpleClass Disposed, tag:{0}", m_text));
}
}
先點擊按鈕1,再點擊按鈕2釋放,我們會發現:
q 方法Method2中的對象先被釋放,雖然它在Method1之后被調用;
q 方法Method2中的對象先被釋放,雖然它不像Method1那樣為對象引用賦值為null;
在CLR托管應用程序中,存在一個根的概念,類型的靜態字段、方法參數以及局部變量都可以作為根存在(值類型不能作為根,只有引用類型的指針才能作為根)。
上面的兩個方法中各自的局部變量,在代碼運行過程中會在內存中各自創建一個根.在一次垃圾回收中,垃圾回收器會沿著線程棧上行檢查根。檢查到方法內的根時,如果發現沒有任何一個地方引用了局部變量,則不管是否為變量賦值為null,都意味著該根已經被停止掉。然后垃圾回收器發現該根的引用為空,同時標記該根可被釋放,這也表示著Simple類型對象所占用的內存空間可被釋放。所以,在上面的這個例子中,為s指定為null絲毫沒有意義(方法的參數變量也是這種情況)。
更進一步的事實是,JIT編譯器是一個經過優化的編譯器,無論我們是否在方法內部為局部變量賦值為null,該語句都會被忽略掉:
s = null;
在我們將項目設置為Release模式下,上面的這行代碼將根本不會被編譯進運行時內。
正式由于上面這樣的分析,很多人認為為對象賦值為null完全沒有必要。但是,在另外一種情況下,卻要注意及時為變量賦值為null。那就是類型的靜態字段。為類型對象賦值為null,并不意味著同時為類型的靜態字段賦值為null:
{
SimpleClass s = new SimpleClass("test");
}
private void button2_Click(object sender, EventArgs e)
{
GC.Collect();
}
}
class SimpleClass
{
static AnotherSimpleClass asc = new AnotherSimpleClass();
string m_text;
public SimpleClass(string text)
{
m_text = text;
}
~SimpleClass()
{
//asc = null;
MessageBox.Show(string.Format("SimpleClass Disposed, tag:{0}", m_text));
}
}
class AnotherSimpleClass
{
~AnotherSimpleClass()
{
MessageBox.Show("AnotherSimpleClass Disposed");
}
}
以上代碼運行的結果使我們發現,當執行垃圾回收,當類型SampleClass對象被回收的時候,類型的靜態字段asc并沒有被回收。
必須要將SimpleClass的終結器中注釋的那條代碼啟用。
字段asc才能被正確釋放(注意,要點擊兩次釋放按鈕。這是因為一次垃圾回收會僅僅首先執行終結器)。之所以靜態字段不被釋放(同時賦值為null語句也不會像局部變量那樣被運行時編譯器優化掉),是因為類型的靜態字段一旦被創建,該根就一直存在。所以垃圾回收器始終不會認為它是一個垃圾。非靜態字段不存在這個問題。將asc改為非靜態,再次運行上面的代碼,會發現asc隨著類型的釋放而被釋放。
上文代碼的例子中,讓asc=null是在終結器中完成的,實際工作中,一旦我們感覺到自己的靜態引用類型參數占用內存空間比較大,并且使用完畢后不再使用,則可以立刻將其賦值為null。這也許并不必要,但這絕對是一個好習慣。試想一下在一個大系統中,那些時不時在類型中出現的靜態變量吧,它們就那樣靜靜地呆在內存里,一旦被創建,就永遠不離開,越來越多,越來越多。