Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is this "pinning handle object[]" that I see in Jetbrains dotMemory when I use a List<T>?

I have tried to come up with the simplest code to reproduce what I am seeing. The full program is below, but I will describe it here. Suppose I have class named ListData that just has some properties. Then suppose I have a MyList class that has a member List<ListData> m_list. Suppose m_list gets initialized in the MyList constructor.

In the main method I simply create one of these MyList objects, add a few ListData to it, then let it go out of scope. I take a snapshot in dotMemory after the ListData have been added, then I take another snapshot after the MyList object goes out of scope.

In dotMemory I can see that the MyList object has been reclaimed as expected. I also see that the two ListData objects that I created also got reclaimed as expected.

What I do not understand is why is there a ListData[] that survived? Here is a screen shot of this: Screenshot of this in dotMemory

I open survived objects on the newest snapshot for the ListData[] then I view Key Retention Paths, this is what I see.

Key Retention Paths

I am new to .NET memory management and I created this sample app to help me explore it. I downloaded the trial version of JetBrains dotMemory version 4.3. I am using Visual Studio 2013 Professional. I have to learn memory management so I can fix the memory issues we have at work.

Here is the full program that can be used to reproduce this. It is just a quick and dirty app but it will get the thing I am asking about if you profile it.

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();
        }
    }
}

Steps:

  1. Build above code in release.
  2. In dotMemory, select to profile a standalone app.
  3. Browse to the release exe.
  4. Select the option to start collecting allocation data immediately.
  5. Click Run.
  6. Take a snapshot immediately and name it "before". This is before any ListData have been added.
  7. In the app, type a and add two ListData.
  8. In dotMemory, take another snapshot and name it "added 2" because we added two ListData.
  9. In the app, type q to quit (the MyList will go out of scope). Before typing Enter again to exit the app, go take another snapshot in dotMemory. Name it "out of scope".
  10. In the app, type Enter to close the app.
  11. In dotMemory, compare the "added 2" and the "out of scope" snapshots. Group by namespace. You will see the ListData[] that I am referring to.

Notice that the MyList and the two ListData objects did get garbage collected but the ListData[] did not. Why is there a ListData[] hanging around? How can I make it get garbage collected?

like image 807
cchampion Avatar asked Jul 06 '15 00:07

cchampion


1 Answers

Why is there a ListData[] hanging around? How can I make it get garbage collected?

If you look at the "Creation Stack Trace" inside dotMemory, you'll see:

dotMemory StackTrace

This shows you that the empty ListData[0] instance was created via the static constructor of List<T>. If you look at the source, you'll see this:

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

List<T> initializes a default, empty array to optimize the avoid such an allocation each time you create a new List<T>. This is the default constructor:

public List() 
{
    _items = _emptyArray;
}

Only one you use List<T>.Add, will it resize the array.

static members are referenced from the "High Frequency Heap", which are created once for each AppDomain in your application. The pinned object[] you're seeing is actually the location where all static instances are stored.

Since the instance is static, it will remain in memory for the lifetime of your application.

like image 79
Yuval Itzchakov Avatar answered Oct 23 '22 02:10

Yuval Itzchakov