即使在需要时也不会发生垃圾收集

我制作了一个64位WPF测试应用程序。 随着我的应用程序运行并打开任务管理器,我会查看系统内存使用情况。 我看到我使用2GB,我有6GB可用。

在我的应用程序中,我单击“添加”按钮将新的1GB字节数组添加到列表中。 我看到我的系统内存使用量增加了1GB。 我点击添加总共6次,填充我开始时可用的6GB内存。

我单击“删除”按钮6次以从列表中删除每个数组。 删除的字节数组不应该由我的控件中的任何其他对象引用。

当我删除时,我没有看到我的记忆下降。 但这对我来说没关系,因为我知道GC是非确定性的,所有这一切。 我认为GC会根据需要收集。

所以现在内存看起来很满,但期望GC在需要时收集,我再次添加。 我的电脑开始滑入和滑出磁盘晃动昏迷。 为什么GC不收集? 如果那不是时候做,那么什么时候?

作为一个完整性检查,我有一个强制GC的按钮。 当我推动它时,我很快就恢复了6GB。 这不能certificate我的6个arrays没有被引用,如果GC知道/想要收集COULD吗?

我已经阅读了很多说我不应该调用GC.Collect()但是如果GC在这种情况下不收集,我还能做什么?

private ObservableCollection memoryChunks = new ObservableCollection(); public ObservableCollection MemoryChunks { get { return this.memoryChunks; } } private void AddButton_Click(object sender, RoutedEventArgs e) { // Create a 1 gig chunk of memory and add it to the collection. // It should not be garbage collected as long as it's in the collection. try { byte[] chunk = new byte[1024*1024*1024]; // Looks like I need to populate memory otherwise it doesn't show up in task manager for (int i = 0; i  0) { memoryChunks.RemoveAt(0); } } private void GCButton_Click(object sender, RoutedEventArgs e) { GC.Collect(); GC.WaitForPendingFinalizers(); } 

作为一个完整性检查,我有一个强制GC的按钮。 当我推动它时,我很快就恢复了6GB。 这不能certificate我的6个arrays没有被引用,如果GC知道/想要收集COULD吗?

你最好不要问When does the GC automatically collect "garbage" memory? 。 脱离我的头顶:

  • 最常见的是,当第0代已满或对象分配不适合可用空闲空间时。 1
  • 通常,当分配一块内存会导致OutOfMemoryException ,会触发一个完整的GC来首先尝试并回收可用内存。 如果在收集后没有足够的连续内存可用,则将抛出OOMexception。

启动垃圾收集时,GC确定需要收集哪些代(0,0 + 1或全部)。 每一代都有一个由GC确定的大小(它可以随着应用程序的运行而改变)。 如果只有第0代将超过其预算,那么这是唯一将收集垃圾的世代。 如果在第0代中幸存的对象将导致第1代超出其预算,那么第1代也将被收集,其幸存的对象将被提升为第2代(这是Microsoft实现中的最高代)。 如果超过第2代的预算,则将收集垃圾,但是不能将对象提升到更高的一代,因为不存在。

因此,这里有重要信息,以最常见的方式启动GC,只有在第0代和第1代都满时才会收集第2代。 此外,您需要知道超过85,000字节的对象不会存储在正常的GC堆中,而是生成0,1和2.它实际上存储在所谓的大对象堆(LOH)中。 LOH中的内存仅在FULL集合期间(即收集第2代时)释放; 从来没有收集过0或1代。

为什么GC不收集? 如果那不是时候做,那么什么时候?

现在应该很明显为什么GC从未自动发生过。 您只是在LOH上创建对象(请记住, int类型,您使用它们的方式,在堆栈上分配,不必收集)。 你永远不会填满第0代,所以GC永远不会发生。 1

你也是在64位模式下运行它,这意味着你不太可能遇到我上面列出的另一种情况,当整个应用程序中没有足够的内存来分配某个对象时会发生集合。 64位应用程序的虚拟地址空间限制为8TB,因此在您遇到此情况之前需要一段时间。 在此之前,您更有可能耗尽物理内存和页面文件空间。

由于没有发生GC,Windows开始从页面文件中的可用空间为您的应用程序分配内存。

我已经阅读了很多说我不应该调用GC.Collect()但是如果GC在这种情况下不收集,我还能做什么?

如果您需要编写此类代码,请调用GC.Collect() 。 更好的是,不要在测试之外编写这种代码。

总之,我还没有公正地对待CLR中的自动垃圾收集主题。 我推荐通过msdn博客文章阅读它(实际上非​​常有趣),或者正如已经提到的那样,Jeffery Richter的优秀书籍CLR Via C#,第21章。


1我假设你明白GC的.NET实现是世代垃圾收集器。 简单来说,这意味着新创建的对象处于编号较低的一代,即生成0.当运行垃圾收集时,发现某一代中的对象具有GC根(不是“垃圾”),它将被提升到下一代。 这是一项性能改进,因为GC可能需要很长时间并且会损害性能。 这个想法是,较高代的物体通常具有较长的寿命,并且在应用中会更长,所以它不需要像下半代那样检查那一代的垃圾。 您可以在此维基百科文章中阅读更多内容 。 你会发现它也被称为短暂的 GC。

2如果您不相信我,在删除其中一个块之后,有一个函数可以创建一大堆随机字符串或对象(我建议对这个测试使用基元数组),你会看到你之后达到一定的空间,将发生一个完整的GC,释放你在LOH中分配的内存。

这是在LOH(大对象堆)上进行的。 只有在执行第2代集合时才会清除它们。 正如汉斯在评论中所说,如果这是真正的代码,你将需要更多的内存。

对于咯咯笑声,您可以调用GC.GetGeneration(chunk)来查看它将返回2

请参阅CLR via C#,Jeffrey Richter的第3版(第588页)。

对于需要分配大量数据而不是发布的真实代码,考虑在完成大对象时手动调用GC( GC.Collect最佳实践问题 )。

您还可以通过在较小(小于80K)的块中进行分配,将对象从LOH转移到普通堆。