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:

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

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:
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?
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:

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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With