当我使用List 时,我在Jetbrains dotMemory中看到的这个“pinning handle object ”是什么?

我试图想出最简单的代码来重现我所看到的。 完整的程序如下,但我将在此处进行描述。 假设我有一个名为ListData ,它只有一些属性。 然后假设我有一个MyList类,它有一个成员List m_list 。 假设m_listm_list构造函数中初始化。

在main方法中,我只需创建其中一个MyList对象,向其中添加一些ListData ,然后让它超出范围。 添加ListData后,我在dotMemory中拍摄快照,然后在MyList对象超出范围后再拍摄另一个快照。

在dotMemory中,我可以看到MyList对象已按预期回收。 我还看到我创建的两个ListData对象也按预期回收。

我不明白为什么有幸存的ListData[] ? 这是一个屏幕截图: dotMemory中的屏幕截图

我在ListData[]的最新快照上打开幸存的对象,然后我查看Key Retention Paths,这就是我所看到的。

关键保留路径

我是.NET内存管理的新手,我创建了这个示例应用程序来帮助我探索它。 我下载了JetBrains dotMemory版本4.3的试用版。 我正在使用Visual Studio 2013 Professional。 我必须学习内存管理,这样才能解决我们在工作中遇到的内存问题。

这是完整的程序,可用于重现这一点。 它只是一个快速而肮脏的应用程序,但如果你对它进行分析,它会得到我要问的东西。

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication1 { class ListData { public ListData(string name, int n) { Name = name; Num = n; } public string Name { get; private set; } public int Num { get; private set; } } class MyList { public MyList() { m = new List(); } public void AddString(ListData d) { m.Add(d); } private List m; } class Program { static void Main(string[] args) { { MyList l = new MyList(); bool bRunning = true; while (bRunning) { Console.WriteLine("a or q"); string input = Console.ReadLine(); switch (input) { case "a": { Console.WriteLine("Name: "); string strName = Console.ReadLine(); Console.WriteLine("Num: "); string strNum = Console.ReadLine(); l.AddString(new ListData(strName, Convert.ToInt32(strNum))); break; } case "q": { bRunning = false; break; } } } } Console.WriteLine("good bye"); Console.ReadLine(); } } } 

脚步:

  1. 在发布中构建上面的代码。
  2. 在dotMemory中,选择配置独立应用程序。
  3. 浏览到发布exe。
  4. 选择立即开始收集分配数据的选项。
  5. 单击运行。
  6. 立即拍摄快照并在“之前”命名。 这是在添加任何ListData之前。
  7. 在应用程序中,键入a并添加两个ListData。
  8. 在dotMemory中,拍摄另一个快照,并将其命名为“添加2”,因为我们添加了两个ListData。
  9. 在应用程序中,键入q以退出(MyList将超出范围)。 在再次输入Enter以退出应用程序之前,请在dotMemory中拍摄另一个快照。 将其命名为“超出范围”。
  10. 在应用程序中,输入Enter以关闭应用程序。
  11. 在dotMemory中,比较“添加的2”和“超出范围”快照。 按命名空间分组。 您将看到我所指的ListData []。

请注意,MyList和两个ListData对象确实收集了垃圾,但ListData []却没有。 为什么会有一个ListData []? 如何让垃圾收集?

为什么会有一个ListData []? 如何让垃圾收集?

如果你看一下dotMemory里面的“Creation Stack Trace”,你会看到:

dotMemory StackTrace

这表明空ListData[0]实例是通过List的静态构造函数创建的。 如果你看一下这个来源 ,你会看到:

 static readonly T[] _emptyArray = new T[0]; 

List初始化一个默认的空数组,以优化每次创建新List时避免这样的分配。 这是默认构造函数:

 public List() { _items = _emptyArray; } 

只有一个使用List.Add ,它会调整数组的大小。

static成员是从“高频堆”引用的,它们为应用程序中的每个AppDomain创建一次。 您看到的固定object[]实际上是存储所有static实例的位置。

由于实例是static ,因此它将在应用程序的生命周期内保留在内存中。