天天看點

改善C#程式的建議5:引用類型指派為null與加速垃圾回收

參考連結:https://www.cnblogs.com/luminji/archive/2011/03/29/1997812.html

本文基于Creative Commons Attribution 2.5 China Mainland License釋出,歡迎轉載,演繹或用于商業目的,但是必須保留本文的署名http://www.cnblogs.com/luminji(包含連結)。如您有任何疑問或者授權方面的協商,請給我留言。

根據這個文章,得出結論是類型的非靜态成員變量不用使用null指派,類型的靜态成員變量需要null指派,但是靜态變量屬于類的,是以隻有一個記憶體配置設定

有一些人認為等于null可以幫助垃圾回收機制早點發現并辨別對象是垃圾。其他人則認為這沒有任何幫助。是否指派為null的問題首先在方法的内部被人提起。現在,為了更好的闡述提出的問題,我們來撰寫一個Winform窗體應用程式。如下:

private void button1_Click(object sender, EventArgs e)
{
	Method1();
	Method2();
}

private void button2_Click(object sender, EventArgs e)
{
	GC.Collect();
}

privatevoid Method1()
{
	SimpleClass s =new SimpleClass("method1");
	s =null;
	//其它無關工作代碼(這條注釋源于回應回複的朋友的質疑)
}
privatevoid 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:

privatevoid button1_Click(object sender, EventArgs e)
{
SimpleClass s =new SimpleClass("test");
}

privatevoid button2_Click(object sender, EventArgs e)      //這個代碼有點問題,gc回收,作業系統沒有立即回收記憶體
{
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的終結器中注釋的那條代碼啟用。

上面代碼

private void button2_Click(object sender, EventArgs e)      //這個代碼有點問題,gc回收,作業系統沒有立即回收記憶體
{
GC.Collect();			//這個不會立即回收靜态變量的記憶體,也就是沒有立即進入析構函數
}
           

修改為,了解回收記憶體,調試效果明顯,說明了null會釋放根引用,在記憶體回收的時候,會釋放靜态變量記憶體

GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
           

字段asc才能被正确釋放(注意,要點選兩次釋放按鈕。這是因為一次垃圾回收會僅僅首先執行終結器)。之是以靜态字段不被釋放(同時指派為null語句也不會像局部變量那樣被運作時編譯器優化掉),是因為類型的靜态字段一旦被建立,該“根”就一直存在。是以垃圾回收器始終不會認為它是一個垃圾。非靜态字段不存在這個問題。将asc改為非靜态,再次運作上面的代碼,會發現asc随着類型的釋放而被釋放。

上文代碼的例子中,讓asc=null是在終結器中完成的,實際工作中,一旦我們感覺到自己的靜态引用類型參數占用記憶體空間比較大,并且使用完畢後不再使用,則可以立刻将其指派為null。這也許并不必要,但這絕對是一個好習慣。試想一下在一個大系統中,那些時不時在類型中出現的靜态變量吧,它們就那樣靜靜地呆在記憶體裡,一旦被建立,就永遠不離開,越來越多,越來越多……。