当我使用List <T>时,我在Jetbrains dotMemory中看到的这个"pinning handle object []"是什么?

cch*_*ion 8 .net c# memory-management

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

在main方法中,我只需创建其中一个MyList对象,向其中添加一些对象ListData,然后让它超出范围.我在添加完成后在dotMemory中拍摄快照ListData,然后在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<ListData>();
        }

        public void AddString(ListData d)
        {
            m.Add(d);
        }

        private List<ListData> 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

脚步:

  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 []?如何让垃圾收集?

Yuv*_*kov 3

为什么有一个 ListData[] 挂在周围?我怎样才能让它被垃圾收集?

如果您查看 dotMemory 内的“创建堆栈跟踪”,您将看到:

点内存堆栈跟踪

这表明空ListData[0]实例是通过 的静态构造函数创建的List<T>如果您查看源代码,您会看到以下内容:

static readonly T[]  _emptyArray = new T[0];    
Run Code Online (Sandbox Code Playgroud)

List<T>初始化默认的空数组以优化,避免每次创建新的List<T>. 这是默认构造函数:

public List() 
{
    _items = _emptyArray;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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