Chris Alton said:
Please read my post carefully before jumping to conclusions.
I stated below that it is "possible" your .NET app can use up to 300MB of
RAM (as viewed in task manager) not that 300MB is the kick in point for
the
Garbage Collector to run. Try loading up multiple large DataSets with
around 50,000 rows on a button click and you'll certainly see the memory
usage of the app exceed 300MB.
Hmm... let me explain my point. The CLR starts a GC run whenever the Gen0
threshold (a variable value depending on some heuristics like the GC type,
CPU L2 cache size and allocation pattern/frequency, in general a value less
than a couple of MB) has been reached, removing non-rooted objects (garbage)
from Gen0 while promoting all still referenced objects to Gen1. This
continuous until Gen1's threshold has been reached, in which case the GC
will remove the non-rooted objects from Gen1 and move all (still) rooted
Gen1 objects to Gen2.
Again this goes on until the Generational heap (Gen0,1 and2) fills-up
completely (32MB), this will trigger a full collection (Gen0,1, 2 and LOH).
When a full collection cannot free enough space to hold another Gen0
quantum, then the CLR will will allocate another memory segment from the
process space in order to extend the GC heap. This process goes on until all
process memory space is consumed (say all objects remain rooted). The
additional segments aren't automatically returned to the OS, the CLR will
only trim it's WS and return empty segments when explicitely requested for
by the OS.
True, a .NET application can use 300MB of RAM (even a lot more), but this
300MB will mostly account for rooted objects like in the sample you gave
with the 50.000 rows datasets, but this doesn't mean that the GC doesn't run
while creating the Dataset(s), right?
"Try with a simple program [1] that allocated a List with string
objects...." and watch the GC counters in Perfmon while you fill the List,
you'll see that the GC runs more often than you may have guessed, and please
don't run this from a SQL/CLR, the SQL host sets the Gen0 threshold to a
higher value than the shell host).
class Program
{
static void Main()
{
Console.ReadKey();
List<string> list = new List<string>(2000000);
for(int i = 0; i < list.Capacity; i++)
list.Add(String.Format("This is a lonooooooooooooooooooooooooooooooog
string {0}", i));
Console.WriteLine(list.Count);
Console.WriteLine("Press any key to continue.");
Console.ReadKey();
}
}
When I run this on my box, the "GC 0 Collections" counter goes from 0 to
123.
Gen1 from 0 to 46 and Gen2 from 0 to 6. Private bytes goes from a couple of
MB to 318MB (your mileage may vary)
You see, the GC ran 175 times while (obviously) nothing was being collected.
Willy.