在.NET中,雖然CLR的GC垃圾回收器幫我們自動回收托管堆對象,釋放內存,最大程度避免了"內存泄漏"(應用程序所占用的內存沒有得到及時釋放),但.NET應用程序"內存泄漏"的問題還是會存在,如果不加以注意,"內存泄漏"時有發生。
有關流以及Reader或Writer引起的內存泄漏
比如,把文件讀取到流中:
public static string ReadFile(){var filePath = @"硬盤地址";var sr = new StreamReader(filePath);return sr.ReadToEnd();}
以上,StreamReader在讀取數據后沒有解釋銷毀,存在"內存泄漏"。正確的做法是在使用完后及時關閉。
public static string ReadFile(){var filePath = @"硬盤地址";using(var sr = new StreamReader(filePath)){return sr.ReadToEnd();}}
或者
public static string ReadFile(){var filePath = @"硬盤地址";var sr = new StreamReader(filePath);var result = sr.ReadToEnd();sr.Close();return result;}
以上,需要我們注意的是:當通過某種流的構造函數創建的對象實例,注意及時關閉。
有時候,通過某個方法返回某種流的對象實例,也會忘記關閉。比如以下:
//創建字節數組var data = new byte[1024];var client = new TcpClient();//從TCP實例方法返回流var stream = client.GetStream();//把流讀到字節數組中int bytesLength = stream.Read(data, 0, data.Length);//字節數組轉換城字符串var result = System.Text.Encoding.ASCII.GetString(data, 0, bytesLength);
正確的寫法應該是:
//創建字節數組var data = new byte[1024];var client = new TcpClient();//從TCP實例方法返回流var stream = client.GetStream();//把流讀到字節數組中int bytesLength = stream.Read(data, 0, data.Length);stream.Close();//字節數組轉換城字符串var result = System.Text.Encoding.ASCII.GetString(data, 0, bytesLength);
同理,其它與流有關的類,我們也需要注意在用完后及時關閉:
● FileStream
● MemoryStream
● StreamReader
● TextWriter
......
靜態引用引起的內存泄漏
對於靜態實例來說,除非應用程序關閉,對應的內存一直得不到釋放。比如有如下遵循"Siingleton"模式的類(沒考慮線程安全)。
public class MySingletonClass{private static MySingletonClass myInstance;private static List<IAmBig> bigObjects = new List<IAmBig>();private MySingletonClass(){}public static MySingletonClass MyInstance{get{if(myInstance == null){myInstance = new MySingletonClass();}return myInstance;}}public static IAmBig CreateBigObject(){var bigObject = new IAmBig();bigobject.AllocateMemory(4096);bigObjects.add(bigObject);return bigObject;}}public class IAmBig{}
以上,每次調用CreateBigObject靜態方法,都往List<IAmBig>類型集合中添加,由於MySingletonClass靜態類實例一直存在於應用程序的生命周期,再加上GC不會釋放bigObjects這個集合對象實例,於是就出現了"內存泄漏"。解決辦法是避免讓靜態實例引用其它實例對象,避免出現靜態實例的鏈式引用。
委托引起的內存泄漏
比如有2個委托形成的委托鏈。
var objectOne = new ObjectOne();var objectTwo = new ObjectTwo();objectOne.StateChanged += objectTwo.StateChangedEventHandler;objectTwo.Dispose();
以上,把objectTwo的委托注冊給了objectOne,這樣objectOne和objectTwo有依賴關系,形成了依賴鏈。只有當objectOne被釋放,才能釋放objectTwo。如果objectOne恰巧是全局靜態實例,那在應用程序的生命周期內,objectTwo一直得不到內存釋放,造成了"內存泄漏"。
解決辦法是在調用objectTwo的Dispose方法之前,先解開兩者的依賴關系。修改如下:
var objectOne = new ObjectOne();var objectTwo = new ObjectTwo();objectOne.StateChanged += objectTwo.StateChangedEventHandler;......objectOne.StateChanged -= objectTwo.StateChangedEventHandler;objectTwo.Dispose();
非托管資源引起的內存泄漏
public class MyUnManagedExample{public void Allocate(){IntPtr pointer = Marshal.AllocHGlobal(1024);}}
對於創建的非托管類型的實例ponter,需要顯式釋放。
Marshal.FreeGlobal(pointer);
實現IDisposable接口的類引起的內存泄漏
所有實現IDisposable接口的類都有一個Dispose方法,如果忘記調用,就造成"內存泄漏"。