zoukankan      html  css  js  c++  java
  • .NET陷阱之三:“正确”使用控件也会造成内存泄露

    在我们的代码中,有时会在控件中添加对数据对象的引用。比如使用树节点的Tag属性保存相应的对象,以便在界面操作中能简单的进行访问。因为其它地方不会引用这些数据,所以我们期望在控件被销毁时,垃圾回收机制能回收相应的内存。但当软件运行了一段时间后,内存使用量会变得非常大。下面是简化后的示例代码: 

     1 using System;
     2 using System.Windows.Forms;
     3 
     4 namespace MemoryLeak
     5 {
     6     public class MainForm : Form
     7     {
     8         private Button holderButton;
     9         private Button controlButton;
    10         private FlowLayoutPanel panel;
    11         private object checkGc;
    12 
    13         public MainForm()
    14         {
    15             DumpMemoryUsage("before allocate checkGc.");
    16             checkGc = MakeLargeObject();
    17             DumpMemoryUsage("after allocate checkGc.");
    18 
    19             holderButton = new Button();
    20             holderButton.Enabled = false;
    21             holderButton.AutoSize = true;
    22             holderButton.Text = "The button holds large object.";
    23             DumpMemoryUsage("before allocate holderButton.Tag.");
    24             holderButton.Tag = MakeLargeObject();
    25             DumpMemoryUsage("after allocate holderButton.Tag.");
    26 
    27             controlButton = new Button();
    28             controlButton.AutoSize = true;
    29             controlButton.Text = "The button controls holderButton.";
    30             controlButton.Click += (sender, e) =>
    31             {
    32                 DumpMemoryUsage("before release checkGc and holderButton.Tag.");
    33                 panel.Controls.Remove(holderButton);
    34                 holderButton.Dispose();
    35                 holderButton = null;
    36 
    37                 checkGc = null;
    38                 DumpMemoryUsage("after release checkGc and holderButton.Tag.");
    39             };
    40 
    41             panel = new FlowLayoutPanel();
    42             panel.AutoSize = true;
    43             panel.FlowDirection = FlowDirection.TopDown;
    44             panel.Controls.Add(controlButton);
    45             panel.Controls.Add(holderButton);
    46 
    47             Controls.Add(panel);
    48         }    
    49 
    50         private void DumpMemoryUsage(string msg)
    51         {
    52             GC.Collect();
    53             Console.WriteLine(msg);
    54             Console.WriteLine(GC.GetTotalMemory(true));
    55         }
    56 
    57         private object MakeLargeObject()
    58         {
    59             var largeObject = new object[100];
    60             for (int i = 0; i < largeObject.Length; ++i)
    61             {
    62                 var array = new int[100][];
    63                 largeObject[i] = array;
    64                 for (int j = 0; j < array.Length; ++j)
    65                 {
    66                     array[j] = new int[100];
    67                 }
    68             }
    69 
    70             return largeObject;
    71         }
    72     }
    73 
    74     static class Program
    75     {
    76         static void Main()
    77         {
    78             Application.Run(new MainForm());
    79         }
    80     }
    81 }

    代码中的checkGc变量是为了在输出中确认垃圾回收已经进行了。下面是输出结果:

     1 before allocate checkGc.
     2 281576
     3 after allocate checkGc.
     4 4605632
     5 before allocate holderButton.Tag.
     6 4606384
     7 after allocate holderButton.Tag.
     8 8930480
     9 before release checkGc and holderButton.Tag.
    10 8940016
    11 after release checkGc and holderButton.Tag.
    12 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的内容。

  • 相关阅读:
    docker compose 笔记
    一个简单的计划
    译Node.js应用的持续部署
    Javascript中的字典和散列
    施耐德保护调试技巧
    施耐德Sepam 40系列备自投逻辑
    请随时告诉自己
    顺其自然
    启用
    我们能做的是......
  • 原文地址:https://www.cnblogs.com/brucebi/p/2997490.html
Copyright © 2011-2022 走看看