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

Buz*_*uzz 25 .net c# 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();
    }
Run Code Online (Sandbox Code Playgroud)

Chr*_*ens 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,只有在第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可能需要很长时间并且会损害性能.我们的想法是,较高代的对象通常具有较长的使用寿命,并且在应用程序中的使用时间更长,因此不需要像下一代那样检查该代的垃圾.您可以在此维基百科文章中阅读更多内容.你会发现它也被称为短暂的 GC.

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


Bry*_*sby 5

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

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

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