即使在需要时也不会进行垃圾收集

Posted

技术标签:

【中文标题】即使在需要时也不会进行垃圾收集【英文标题】:Garbage Collection not happening even when needed 【发布时间】:2012-04-18 11:32:47 【问题描述】:

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

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

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

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

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

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

我读过很多文章说我不应该调用 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: 01", 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();
    

【问题讨论】:

大对象堆的行为在任何关于 CLR 的背景书籍中都有很好的描述。访问您的图书馆并查看 Richter 的任何作品。如果您在 real 代码中分配许多 1 GB 数组,那么您将需要更多 RAM。任何机器都可能被人工代码卡住。 你也可以从这个链接开始:msdn.microsoft.com/en-us/magazine/cc534993.aspx 【参考方案1】:

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

您最好询问When does the GC automatically collect "garbage" memory?。在我脑海中浮现:

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

开始垃圾回收时,GC 会确定需要回收哪些代(0、0+1 或全部)。每一代都有一个由 GC 确定的大小(它可以随着应用程序的运行而改变)。如果只有第 0 代会超出其预算,那是唯一将收集垃圾的代。如果第 0 代幸存的对象会导致第 1 代超出其预算,那么第 1 代也将被收集并将其幸存对象提升到第 2 代(这是微软实现中的最高代)。如果也超出了第 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 可能需要很长时间并损害性能。这个想法是,较高代的对象通常具有更长的寿命,并且在应用程序中的存在时间更长,因此它不需要像较低代那样检查该代是否存在垃圾。您可以在this wikipedia article 阅读更多内容。您会注意到它也称为 临时 GC。

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

【讨论】:

【参考方案2】:

对于需要分配大量数据而不是释放它的实际代码,请考虑在处理完巨大对象后手动调用 GC (GC.Collect best practice question)。

您还可以将对象从 LOH 转移到普通堆,方法是在更小(小于 80K)的块中分配。

【讨论】:

【参考方案3】:

这是在 LOH(大对象堆)上进行的。这些仅在执行第 2 代收集时被清除。正如 Hans 在他的评论中所说,如果这是真实代码,您将需要更多 RAM。

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

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

【讨论】:

以上是关于即使在需要时也不会进行垃圾收集的主要内容,如果未能解决你的问题,请参考以下文章

GC垃圾回收机制

GC垃圾回收机制

SCJP 想知道对象何时被垃圾收集的问题?

JVM详解——垃圾回收算法

垃圾回收机制算法分析

JVM垃圾收集算法的选择