.NET: When does GC run? Memory leak? [closed]
I understand what immutability is, and how the String .NET class is special. The immutability makes it behave like a value type even though it's a reference type. Got it. The C# reference emphasizes this point (see string (C# Reference), emphasis added by me:
Strings are immutable--the contents of a string object cannot be changed after the object is created, although the syntax makes it appear as if you can do this. For example, when you write this code, the compiler actually creates a new string object to hold the new sequence of characters, and that new object is assigned to b. The string "h" is then eligible for garbage collection.
Being a self-taught programmer I'm not well versed in garbage collectors and memory leaks and pointers and stuff. That's why I'm asking a question about it. The description of how the C# compiler automatically creates new string objects and abandons old ones makes it seem like a bunch of memory could get used up with abandoned string content. A lot of objects have dispose methods or destructors so that even the automated CLR garbage collector knows when and how to clean up after an object that isn't needed anymore. There is nothing like this for a String. I wanted to see what would actually happen if a created a program so that I could demostrate for myself and others that creating and immediately abandoning string objects can consume a lot of memory.
Here's the program:
class Program {
static void Main(string[] args)
{
Console.ReadKey();
int megaByte = (int)Math.Pow(1024, 2);
string[] hog = new string[2048];
char c;
for (int i = 0; i < 2048; i++)
{
c = Convert.ToChar(i);
Console.WriteLine("Generating iteration {0} (char = '{1}')", i, c);
hog[i] = new string(c, megaByte);
if ((i + 1) % 256 == 0) {
for (int j = (i - 255); j <= i; j++) { hog[j] = hog[i]; } }
}
Console.ReadKey();
List<string> uniqueStrings = new List<string>();
for (int i = 0; i < 2048; i++) {
if (!uniqueStrings.Contains(hog[i])) { uniqueStrings.Add(hog[i]); }
}
Console.WriteLine("There are {0} unique strings in hog.", uniqueStrings.Count);
Console.ReadKey();
// Create a timer with an interval of 30 minutes
// (30 minutes * 60 seconds * 1000 milliseconds)
System.Timers.Timer t = new System.Timers.Timer(30 * 60 * 1000);
t.Elapsed += new System.Timers.ElapsedEventHandler(t_Elapsed);
t.Start();
Console.WriteLine("Waiting 30 minutes...");
Console.ReadKey();
}
static void t_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Console.WriteLine("Time's up. I'm collecting the garbage.");
GC.Collect();
}
}
It creates a destroys bunch of unique strings and only ends up with 8 unique strings in the hog array. In my tests the process was still holding on to 570 Mb to 1.1 Gb (it varied). The timer part waits 30 minutes while leaving the process active (not sleeping) and at the end of 30 minutes, the process was still holding on to all the extra memory until I forced collection. This makes it seem like the .NET garbage collector missed something. Lots of other places people say that calling GC.Collect() is something terrible. So the fact that the memory only seems to get reclaimed by forcing the collector using this method still makes it seem like something is wrong.
Your post is quite a bit of bloat. In short you allocate a lot of memory while keeping references and then notice that the GC won't triggers even when they are no longer referenced, even when much time passes.
This means that the GC isn't triggered based on time, but just based on what allocations happen. And once no allocations happen it won't run. If you start allocating again the memory will eventually go down.
This is in no way related to immutability or strings in particular.
Immutability means that
string a = "abc";
string b = a;
a=a+"def";
creates a new string containing "abcdef", and assigns it to a
. The value of b
is unchanged, and still refers to a string containing "abc".
Just that the the garbage collector of your version of the CLR reacts to memory pressure, not elapsed time. Why spend precious CPU time cleaning up memory that isn't needed anyway?
Sure you could argue that it would be better to perform the GC when the program is "idle". The problem is, that you want to keep the GC algorithm as simple as possible (which usually means fast) and that the algorithm can't read your mind. It doesn't know when the application is doing "nothing constructive".
精彩评论