垃圾回收(GC)
垃圾回收即Garbage Collector,垃圾指的是內存中已經不會再使用的對象,通過收集釋放掉這些對象占用的內存。
GC以應用程序的root為基礎,遍歷應用程序在Heap上動態分配的所有對象,通過識別它們是否被引用來確定哪些對象是已經死亡的、哪些仍需要被使用。已經不再被應用程序的root或者別的對象所引用的對象就是已經死亡的對象,即所謂的垃圾,需要被回收。
關於C#使用的垃圾回收算法可以點擊這里查看。
析構函數
析構函數會在GC執行清楚當前對象時被調用,可以在析構函數中執行一些釋放方法。
代
為了優化GC算法,微軟使用了“代”的概念,介紹如下:
堆里面總共有3代。
譬如,當程序運行時,有對象需要存儲在堆里面,GC就會創建第1代(假設空間大小為256K),對象就會存儲在第0代里面,當程序繼續運行,運行到第0代的大小不足以存放對象,這時候就就會創建第1代(假設空間為10M),GC就會把第0代里面的“垃圾對象”清理掉,把“活着”的對象放在第1代,這時候第0代就空了,用於存放新來的對象,當第0代滿了的時候,就會繼續執行以上操作,隨着程序的運行,第1代不能滿足存放要求,這時候就會創建第2代,清理方式如上相同。
我們來看一個例子:
1 using System; 2 3 namespace Study 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 Test test = new Test(); 10 11 //對象會被分配到第 0 代 12 Console.WriteLine("test對象所在的代:" + GC.GetGeneration(test)); 13 //回收對象, 這時 test 會被分配到第 1 代 14 GC.Collect(); 15 Console.WriteLine("test對象所在的代:" + GC.GetGeneration(test)); 16 //回收對象, 這時 test 會被分配到第 2 代 17 GC.Collect(); 18 Console.WriteLine("test對象所在的代:" + GC.GetGeneration(test)); 19 //回收對象, 最多只有3個代, 所以 test 還在第 2 代 20 GC.Collect(); 21 Console.WriteLine("test對象所在的代:" + GC.GetGeneration(test)); 22 23 //斷開引用, 對象會被回收 24 test = null; 25 //回收對象, test 會被回收並調用析構函數 26 GC.Collect(); 27 28 Console.Read(); 29 } 30 } 31 32 public class Test 33 { 34 ~Test() 35 { 36 Console.WriteLine("Test被回收了!"); 37 } 38 } 39 }
運行結果如下:
1 test對象所在的代:0 2 test對象所在的代:1 3 test對象所在的代:2 4 test對象所在的代:2 5 Test被回收了!
何時GC
.Net何時執行GC在《C#高級編程》書中也只是簡單的一句“垃圾回收會在運行庫認為需要他時運行。”帶過,總體而言,GC運行策略已經被微軟進行優化過了,我們不需要過多的關心即可。
托管對象和非托管對象
在C#中,很大部分的對象都是托管對象,托管對象的釋放直接由GC來處理,但也存在部分非托管對象,這些對象GC是不能對其進行自動回收的。
非托管對象
即不受運行時管理的資源對象(如窗口句柄 (HWND)、數據庫連接等)。
非托管的對象如下:ApplicationContext,Brush,Component,ComponentDesigner,Container,Context,Cursor,FileStream,Font,Icon,Image,Matrix,Object,OdbcDataReader,OleDBDataReader,Pen,Regex,Socket,StreamWriter,Timer,Tooltip ,文件句柄,GDI資源,數據庫連接等等資源。
比如:當我們使用一個System.IO.StreamReader的一個文件對象,必須顯示的調用對象的Close()方法關閉它,否則會占用系統的內存和資源,而且可能會出現意想不到的錯誤。
Finalize
當我們聲明一個析構函數時實際上編譯器就會自動添加下面的代碼:
1 ~Test() 2 { 3 try{ 4 Finalize(); 5 }finally{ 6 base.Finalize(); 7 } 8 }
如果在派生類中不存在析造函數,卻重載了基類的終結器:
1 protected override void Finalize() 2 { 3 }
垃圾回收時,GC找不到構造函數,會直接調用終結器。
如果沒有顯示釋放資源時,GC時可以靠該方法進行隱式的釋放資源。
Dispose
相對於重寫Finalize方法,實現IDisposable接口來進行顯示的釋放資源是更好的一種方式。
我們只需要實現IDisposable接口即可,我們看看官網提供的常規寫法:
1 using System; 2 3 class BaseClass : IDisposable 4 { 5 // Flag: Has Dispose already been called? 6 bool disposed = false; 7 8 // Public implementation of Dispose pattern callable by consumers. 9 public void Dispose() 10 { 11 Dispose(true); 12 GC.SuppressFinalize(this); 13 } 14 15 // Protected implementation of Dispose pattern. 16 protected virtual void Dispose(bool disposing) 17 { 18 if (disposed) 19 return; 20 21 if (disposing) { 22 // Free any other managed objects here. 23 // 24 } 25 26 // Free any unmanaged objects here. 27 // 28 disposed = true; 29 } 30 }
https://msdn.microsoft.com/zh-cn/library/system.idisposable(v=vs.110).aspx
https://msdn.microsoft.com/zh-cn/library/fs2xkftw(v=vs.110).aspx
弱引用
我們都知道當一個對象存在一個或多個引用時,GC是不會釋放該對象的,如下:
Object obj = new Object();
這種引用稱為強引用。
但是我們可以想一下,還有一種情況是對象稍后可能被使用,但不是很確定是否會使用時,就可以使用弱引用了。
弱引用可以理解為:如果一個對象只存在一個或多個弱引用而沒有強引用時,則GC可以對其進行垃圾回收。
那么在C#中該如何使用弱引用呢?
WeakReference和WeakReference<T>
C#提供了兩個類來實現弱引用的功能,我們只需要將對象裝入該類的實例中,則可以理解為為這個對象添加了一個弱引用,下面給出幫助文檔地址:
WeakReference:https://msdn.microsoft.com/zh-cn/library/system.weakreference(v=vs.110).aspx
WeakReference<T>:https://msdn.microsoft.com/zh-cn/library/gg712738(v=vs.110).aspx
我們再看一個例子:
1 using System; 2 3 namespace Study 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 //強引用 10 Test test = new Test(); 11 //弱引用 1 12 WeakReference weak1 = new WeakReference(test); 13 //弱引用 2 14 WeakReference<Test> weak2 = new WeakReference<Test>(test); 15 16 //存在強引用不會被回收 17 GC.Collect(); 18 19 //注意 temp 也是一個強引用 20 Test temp; 21 22 Console.WriteLine("weak1: " + weak1.IsAlive + ", weak2: " + weak2.TryGetTarget(out temp)); 23 24 //解除所有強引用 25 test = null; 26 temp = null; 27 28 //沒有強引用會被回收 29 GC.Collect(); 30 31 Console.WriteLine("weak1: " + weak1.IsAlive + ", weak2: " + weak2.TryGetTarget(out temp)); 32 33 Console.Read(); 34 } 35 } 36 37 public class Test 38 { 39 ~Test() 40 { 41 Console.WriteLine("Test被回收了!"); 42 } 43 } 44 }
結果如下:
1 weak1: True, weak2: True 2 weak1: False, weak2: False 3 Test被回收了!
