.NET:GC什么时候运行?内存泄漏?
我了解什么是不变性,以及 String .NET 类的特殊之处。尽管它是引用类型,但不变性使其表现得像值类型。知道了。 C#参考强调了这一点(参见字符串(C#参考),强调我添加的:
字符串是不可变的——字符串对象的内容在创建对象后就无法更改,尽管语法使您看起来好像可以这样做。例如,当您编写此代码时,编译器实际上创建一个新的字符串对象来保存新的字符序列,并将该新对象分配给 b。然后字符串“h”就可以进行垃圾回收。
作为一名自学成才的程序员,我不太熟悉垃圾收集器、内存泄漏、指针之类的东西。这就是为什么我要问这个问题。 C# 编译器如何自动创建新的字符串对象并放弃旧的字符串对象的描述让人感觉一堆内存可能会被放弃的字符串内容耗尽。许多对象都有 dispose 方法或析构函数,这样即使是自动 CLR 垃圾收集器也知道何时以及如何清理不再需要的对象。对于字符串来说没有这样的东西。我想看看如果创建一个程序会实际发生什么,以便我可以为自己和其他人演示创建并立即放弃字符串对象会消耗大量内存。
程序如下:
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();
}
}
它创建了一堆销毁的唯一字符串,最终在 hog 数组中仅包含 8 个唯一字符串。在我的测试中,该进程仍然保留 570 Mb 到 1.1 Gb(有所不同)。计时器部分等待 30 分钟,同时让进程保持活动状态(不休眠),在 30 分钟结束时,进程仍然保留所有额外内存,直到我强制收集。这使得 .NET 垃圾收集器看起来漏掉了一些东西。很多其他地方的人都说调用 GC.Collect() 是一件可怕的事情。因此,内存似乎只能通过强制收集器使用此方法来回收,这一事实仍然让人感觉有些问题。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
你的帖子有点臃肿。简而言之,您在保留引用的同时分配了大量内存,然后注意到即使不再引用它们,即使经过很长时间,GC也不会触发。
这意味着 GC 不是根据时间触发的,而是根据发生的分配情况触发的。一旦没有分配发生,它就不会运行。如果您再次开始分配,内存最终会减少。
这与不变性或特别的字符串没有任何关系。
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.
不变性意味着
创建一个包含“abcdef”的新字符串,并将其分配给
a
。b
的值没有改变,仍然引用包含“abc”的字符串。Immutability means that
creates a new string containing "abcdef", and assigns it to
a
. The value ofb
is unchanged, and still refers to a string containing "abc".只是您的 CLR 版本的垃圾收集器对内存压力做出反应,而不是对经过的时间做出反应。为什么要花费宝贵的 CPU 时间来清理根本不需要的内存呢?
当然,您可能会争辩说,当程序“空闲”时执行 GC 会更好。问题是,您希望 GC 算法尽可能简单(这通常意味着速度很快),而该算法无法读懂您的想法。它不知道应用程序何时“没有做任何建设性的事情”。
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".