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