GC.Collect() 的目的

The purpose of GC.Collect()

我是 .NET 和 CLR 的新手,只是一个关于 CLR 垃圾的问题 collection。

我的教科书描述了使用 GC.Collect() 的目的之一是

您的应用程序刚刚完成分配大量 objects 并且您想尽快删除尽可能多的获得的内存。

下面是我的代码:

Console.WriteLine("Estimated bytes on heap: {0}", GC.GetTotalMemory(false));
Car refToMyCar = new Car();
Console.WriteLine("\nGeneration of refToMyCar is: {0}", GC.GetGeneration(refToMyCar));
GC.Collect(0, GCCollectionMode.Forced);
Console.WriteLine("\nGeneration of refToMyCar is: {0}", GC.GetGeneration(refToMyCar));
Console.WriteLine("Estimated bytes on heap: {0}", GC.GetTotalMemory(false));

结果是:

Estimated bytes on heap: 29900

Generation of refToMyCar is: 0

Generation of refToMyCar is: 1

Estimated bytes on heap: 39648

所以这是我的问题:

1- GC.Collect() 似乎只标记了 refToMyCar 从第 0 代到第 1 代的项目,没有任何东西 "free",因为第 1 代意味着 object幸免于难collection。假设在 GC.Collect() 之前,堆上还剩下 10mb 的可用空间(例如总共 100mb),而在 GC.Collect() 之后,堆上仍然只剩下 10mb,那是什么指向调用GC.Collect()?我们不希望可用大小为 100mb 100% 可用吗?

编辑: 放弃我之前的问题

如果改成object数组就更奇怪了:

Console.WriteLine("Estimated bytes on heap: {0}", GC.GetTotalMemory(false));
object[] refToMyCar = new object[50000];
for (int i = 0; i < 50000; i++)
   refToMyCar[i] = new object();
Console.WriteLine("\nGeneration of refToMyCar is: {0}", GC.GetGeneration(refToMyCar));       
Console.WriteLine("Estimated bytes on heap: {0}", GC.GetTotalMemory(false));

输出是:

Estimated bytes on heap: 29900

Generation of refToMyCar is: 2
Estimated bytes on heap: 836140
Press any key to continue . . .

为什么 refToMyCar 是第 2 代,它是一个 object 已经在垃圾收集器的一次以上清扫中幸存下来的?我们还没有调用任何隐式或显式 GC.Collect() 吗?

在你的 GC.Collect() 之后你使用了你试图收集的变量:

Console.WriteLine("Estimated bytes on heap: {0}", GC.GetTotalMemory(false));
Car refToMyCar = new Car();
Console.WriteLine("\nGeneration of refToMyCar is: {0}", GC.GetGeneration(refToMyCar));
GC.Collect(0, GCCollectionMode.Forced);

// after removing reference to the variable it's been collected and will not survive to generation 1
// Console.WriteLine("\nGeneration of refToMyCar is: {0}", GC.GetGeneration(refToMyCar));
Console.WriteLine("Estimated bytes on heap: {0}", GC.GetTotalMemory(false));

发生这种情况是因为内存管理的执行方式以及如果您在代码中的某处引用了变量 - 它会继续存在并且它的生成可能会更新。这就是为什么你应该小心闭包和树结构。

输出:

Estimated bytes on heap: 29988

Generation of refToMyCar is: 0
Estimated bytes on heap: 29448

还有关于你的其他问题——我相信你不能释放 100% 的堆内存,只是因为你必须将程序本身存储在某个地方,静态 class 内存分配——GC 是一个很好的例子它。

不允许垃圾收集器收集仍被引用的对象。由于您保留对 refToMyCar 的引用以便稍后获取它的生成,因此无法收集它。如果你想观察被回收的对象,你可以使用 WeakReference 代替。此外,您需要在没有调试器的情况下 运行 - 为了帮助调试,调试器使所有引用都存在,直到它们超出范围(即 block/method 主体结束)。

在第二种情况下,您正在分配一个大对象。 .NET 会将它们放在一个特殊的堆上,即大型对象堆。这些对象有特殊规则——它们始终被视为第二代,并且不能移动(除非您明确要求 GC 这样做)。大对象需要特别注意

当然,这两种行为在documentation中都有描述。

处理 GC.Collect 的基本规则非常简单——不要使用它。这样做有任何好处的情况很少,而且大多数情况下,您只是在浪费 CPU、内存并使对象比其他方式存活 更长