簡介
以前認為,.NET程序內存都是托管的,如果不是調用非托管資源,應該不會存在內存泄漏的問題,但是,最近兩天對歸檔程序內存使用分析,發現,事情不是想象的那么簡單。
.NET內存泄漏,更准確的說應該是對象超過生命周期而不能被GC回收。本文列舉了幾種可能導致內存泄漏的情形,並提供示例代碼,及解決方案,希望對大家有所幫助。
所舉的例子都是經過自己極端簡化過的,只是為了說明觀點,具體項目中遇到的情形會復雜很多,需要具體分析。
事件注冊后未解除注冊
示例代碼
程序運行時,內存使用情況如圖:
原因分析
示例代碼中,表面看起來,一次循環過后,上次new出來的對象a,不會繼續被引用到,應該被視為垃圾,下次垃圾回收的時候,會被回收掉。但是,通過實驗證明,對象a是不會被回收掉的。
原因是因為,對象b注冊了click事件而未銷毀,導致對象b會一直引用到a,a不會被視為垃圾被GC回收。
解決辦法
對象a生命周期結束后,解除事件的注冊,只有這樣,a所占用的內存才會在下次垃圾回收的時候被回收掉。
下圖為解除事件注冊綁定后,內存的使用情況:
靜態引用
示例代碼
如果一直調用CreateObject方法,程序運行時,內存使用情況如圖:
原因分析
靜態變量的生命周期是全局的,即程序不退出,所占用的內存一直不會被釋放掉。
解決辦法
盡量少用靜態成員,若一定要使用,注意靜態成員引用的對象是否會一直增長。
控件不使用后未銷毀
示例代碼
程序運行時,內存使用情況如圖:
原因分析
示例代碼中,控件lbl已經被移除了,表面看起來,下次循環時,該控件應該被視為垃圾,在下次垃圾回收時,被回收掉。但實驗證明,內存不會被回收掉。原因在於,控件未被真正銷毀掉。
解決辦法
控件生命周期結束之后,調用Dispose方法,確保控件能被真正銷毀掉。
下圖為調用Dispose方法后,內存的使用情況:
調用非托管資源而未釋放
示例代碼
原因分析
使用未托管對象之后忘記釋放。
解決辦法
對於實現了IDisposable接口的類可以使用using方式使用,或者顯示調用Dispose方法。
工具
日志輸出
窮人的工具,輸出進程內存使用量 :)
任務管理器
通過觀察任務管理器內存使用情況,查看是否存在內存泄露。
.NET Memory Profiler
專業的.NET性能調優工具,缺點,不是免費的 :)
參考
- http://madgeek.com/articles/leaks/leaks.en.html
- http://stackoverflow.com/questions/620733/memory-leak-in-c-sharp