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

时间:2012-04-04 17:47:06

标签: c# .net memory garbage-collection

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

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

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

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

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

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

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

    private ObservableCollection<byte[]> memoryChunks = new ObservableCollection<byte[]>();
    public ObservableCollection<byte[]> 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 < chunk.Length; i++)
            {
                chunk[i] = 100;
            }

            this.memoryChunks.Add(chunk);                
        }
        catch (Exception ex)
        {
            MessageBox.Show(string.Format("Could not create another chunk: {0}{1}", Environment.NewLine, ex.ToString()));
        }
    }

    private void RemoveButton_Click(object sender, RoutedEventArgs e)
    {
        // By removing the chunk from the collection, 
        // I except no object has a reference to it, 
        // so it should be garbage collectable.

        if (memoryChunks.Count > 0)
        {
            memoryChunks.RemoveAt(0);
        }
    }

    private void GCButton_Click(object sender, RoutedEventArgs e)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }

3 个答案:

答案 0 :(得分:18)

  

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

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

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

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

所以,这里有重要的信息,在GC启动的大多数 common 方式中,只有在第0代和第1代都满时才会收集第2代。此外,您需要知道超过85,000个字节的对象不会存储在具有第0代,第1代和第2代的普通GC堆中。它实际上存储在所谓的大对象堆(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可能需要很长时间并且会损害性能。我们的想法是,较高代的物体通常具有较长的寿命,并且在应用中会更长时间,所以它不需要像下一代那样检查那一代的垃圾。您可以在this wikipedia article中阅读更多内容。您会注意到它也被称为短暂的 GC。

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

答案 1 :(得分:5)

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

对于咯咯笑声,您可以致电GC.GetGeneration(chunk),看看它会返回2

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

答案 2 :(得分:2)

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

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