在我們的代碼中,有時會在控件中添加對數據對象的引用。比如使用樹節點的Tag屬性保存相應的對象,以便在界面操作中能簡單的進行訪問。因為其它地方不會引用這些數據,所以我們期望在控件被銷毀時,垃圾回收機制能回收相應的內存。但當軟件運行了一段時間後,內存使用量會變得非常大。下面是簡化後的示例代碼:
using System; using System.Windows.Forms; namespace MemoryLeak { public class MainForm : Form { private Button holderButton; private Button controlButton; private FlowLayoutPanel panel; private object checkGc; public MainForm() { DumpMemoryUsage("before allocate checkGc."); checkGc = MakeLargeObject(); DumpMemoryUsage("after allocate checkGc."); holderButton = new Button(); holderButton.Enabled = false; holderButton.AutoSize = true; holderButton.Text = "The button holds large object."; DumpMemoryUsage("before allocate holderButton.Tag."); holderButton.Tag = MakeLargeObject(); DumpMemoryUsage("after allocate holderButton.Tag."); controlButton = new Button(); controlButton.AutoSize = true; controlButton.Text = "The button controls holderButton."; controlButton.Click += (sender, e) => { DumpMemoryUsage("before release checkGc and holderButton.Tag."); panel.Controls.Remove(holderButton); holderButton.Dispose(); holderButton = null; checkGc = null; DumpMemoryUsage("after release checkGc and holderButton.Tag."); }; panel = new FlowLayoutPanel(); panel.AutoSize = true; panel.FlowDirection = FlowDirection.TopDown; panel.Controls.Add(controlButton); panel.Controls.Add(holderButton); Controls.Add(panel); } private void DumpMemoryUsage(string msg) { GC.Collect(); Console.WriteLine(msg); Console.WriteLine(GC.GetTotalMemory(true)); } private object MakeLargeObject() { var largeObject = new object[100]; for (int i = 0; i < largeObject.Length; ++i) { var array = new int[100][]; largeObject[i] = array; for (int j = 0; j < array.Length; ++j) { array[j] = new int[100]; } } return largeObject; } } static class Program { static void Main() { Application.Run(new MainForm()); } } }
代碼中的checkGc變量是為了在輸出中確認垃圾回收已經進行了。下面是輸出結果:
before allocate checkGc. 281576 after allocate checkGc. 4605632 before allocate holderButton.Tag. 4606384 after allocate holderButton.Tag. 8930480 before release checkGc and holderButton.Tag. 8940016 after release checkGc and holderButton.Tag. 4616824
由第4行的輸出可以看出,代碼中創建的每個大對象占用了大約4M的內存。問題在於,我們在代碼的第32-38行中已經將holderButter從panel中移除,調用了其Dispose方法,將其設置為null,另外也將checkGc設置為null。但第12的的輸出卻表明,只有一個大對象被回收了!為了找出問題所在,我使用ANTS Memory Profier查看了相應的內存使用情況,如下圖所示:
從中可以看出,確實有一個對象沒有被回收。繼續查看此對象的引用鏈:
原來是Control.cachedLayoutEventArgs在作怪!
現在問題比較清楚了:雖然我們已經銷毀了holderButton,並不再引用它,但是.NET的內部代碼仍然在引用它,而holderButton.Tag所引用的對象自然也不能被回收了。
針對我們的問題,只需要在36行的位置加上holderButton.Tag = null就可以了。而更通用的情況,則應該在Disposed事件中(或重寫相應的方法)將對數據的引用設置為null。
在網上搜索cachedLayoutEventArgs,發現也有人遇到相關的問題,可以參考http://book.3you.cc/bc/Print.asp?ArticleID=297177的內容。
出處:http://www.cnblogs.com/brucebi/archive/2013/04/03/2997490.html