假设我有以下代码......
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
MyCustomClass myObj = new MyCustomClass();
sb.Append(myObj.RenderShortString());
}
Console.Write(sb.ToString());
并假设MyCustomClass是一个非常大的对象。例如,假设它创建并保存一个包含1MB字符串的内部成员。 RenderShortString()方法只是呈现一个长度约为100个字符的字符串。
注意这循环10000次。
我有一些基本上像这样的东西在循环中导致System.OutOfMemory异常。
我的问题与垃圾收集器清理为myObj的每个实例分配的内存空间的时间有关。我不认为我遇到了StringBuilder的问题,但我可能错了。我感觉myObj的实例正在内存中分配,但是在退出循环之后才能进行清理。它是否正确?如果是这样,我怎么能告诉应用程序一旦我得到我的渲染字符串,我就完成了那个实例?
答案 0 :(得分:5)
您正在.net中看到垃圾收集的“功能”。一旦超出范围,对象将被销毁,并且每次myObj在每次迭代时都超出范围但是你不知道GC何时是非确定性的。
以下是一些解释:Loops and Garbage Collection
此外,这是一项关于.net的GC的有趣研究。它建议尽可能避免在循环内使用“new”。
http://nerds-central.blogspot.com/2008/10/net-garbage-collector-pain.html
答案 1 :(得分:4)
简单回答:你永远不会知道。 .NET垃圾收集不是确定性的。您可以使用System.GC.Collect
方法强制执行垃圾回收。除此之外,GC仅保证分配给无法访问的对象的内存最终将被释放。
答案 2 :(得分:2)
你可能真的想再看一下StringBuilder的表现,因为这也可能非常耗费内存。
也许不是根本问题,如果你说MyCustomClass的内存很重,但它可能有助于推动整个过程。
每次StringBuilder空间不足时,它会重新分配两倍于原始缓冲区大小的新缓冲区,复制旧字符,并让旧缓冲区得到GC。您可能只是使用足够的(称之为x),使得2x大于您允许分配的内存。您可能想要确定字符串的最大长度,并将其传递给StringBuilder的构造函数,以便您预先分配,并且您不会受到双倍重新分配的支配。
答案 3 :(得分:2)
在MyCustomClass
完成后,每个RenderShortString()
实例都有资格进行收集 - 在某些情况下执行该方法时甚至。
实际的垃圾收集只会在垃圾收集器的情况下发生,但很可能很快会从gen0收集MyCustomClass
个实例。
请注意,MyCustomClass
对象本身不大,只是因为它们引用了大字符串。那些大字符串将在large object heap上分配,这仍然是垃圾收集但未压缩。如果你发现你的应用程序占用了相当多的内存,很可能是MyCustomClass
的实例已被垃圾收集,但字符串却没有。
在.NET中创建一个非常大的自定义对象实际上非常困难。明显的例子是:
在大多数情况下,对象本身非常小,但是有很多对象。
答案 4 :(得分:1)
您应该运行一个分析器来查看哪些对象分配了多少内存。 Visual Studio的分析器将为您提供该数字,甚至每代中有多少个对象。