前言
今天查看一個同事的代碼,發現代碼中多處地方使用了GC.Collect()方法,我問他為什么這么做,他說感覺程序中定義了好多變量,怕GC回收不及時,用GC.Collect()可以手動掌控GC進行垃圾回收。
先不說他對GC的垃圾回收機制還不了解,就是調用GC.Collect()后GC真的會不會回收這個問題都需要再深入了解一下。
GC.Collect
下面我們通過一個小例子,來看一下使用GC.Collect后的內存情況。
我們知道可以通過GCHandle設置引用類型(可直接復制到本機結構中的類型)在GC垃圾回收時不移動地址,並且獲取地址值,那么就可以通過在兩次地址獲取中間加入Collect方法,來判斷GC是否真的進行了垃圾回收。
using System; using System.Runtime.InteropServices; namespace TestGCCollect { class Program { static void Main(string[] args) { //創建一個沒有引用的垃圾對象 new object(); //這是我們要判定地址的對象 int[] gcTest = new int[10]; //設定Pinned通知GC在進行回收的時候不移動地址 GCHandle gcHandle1 = GCHandle.Alloc(gcTest, GCHandleType.Pinned); //獲取gcTest在堆中的地址並輸出 IntPtr add1 = gcHandle1.AddrOfPinnedObject(); Console.WriteLine(add1.ToString()); //通知GC當程序返回的時候可以回收 gcHandle1.Free(); //調用GC回收object垃圾 GC.Collect(); //再次獲取地址 GCHandle gcHandle2 = GCHandle.Alloc(gcTest, GCHandleType.Pinned); IntPtr add2 = gcHandle2.AddrOfPinnedObject(); Console.WriteLine(add2.ToString()); gcHandle2.Free(); Console.ReadKey(); } } }
我們發現地址並沒有變化!
修改一下代碼使用for循環生成多個object:
//創建沒有引用的垃圾對象 for (int i = 0; i < 30000; i++) new object(); //這是我們要判定地址的對象 int[] gcTest = new int[10];
重新編譯后,執行結果如下:
地址變了!
通過上面的代碼,我們知道GC.Collect並不是只要執行就會進行垃圾回收,實際上GC會首先判斷當前是不是真的需要進行回收,如果內存中只有很小的垃圾(碎片化不嚴重)時,這時候啟動回收顯然得不償失,影響性能。
總結
1. 永遠都不要手動進行GC.Collect操作。如果你認為有,需要檢查你地代碼
2. 即使當你手動進行垃圾回收時,GC還不會立即執行,它要先判斷是否真正需要回收