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

标签 c# .net memory garbage-collection

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

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

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

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

所以现在内存看起来已满,但希望 GC 在需要时收集,我再次添加。
我的 PC 开始滑进滑出,进入磁盘抖动昏迷状态。
为什么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: {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();
    }

最佳答案

As a sanity check, I have a button to force GC. When I push that, I quickly get 6GB back. Doesn't that prove my 6 arrays were not being referenced and COULD have been collected had the GC knew/wanted to?



您最好询问 When does the GC automatically collect "garbage" memory? .在我的头顶:
  • 最常见的是,当第 0 代已满或对象分配不适合可用的可用空间时。1
  • 有点常见的是,当分配一块内存时会导致 OutOfMemoryException , 触发 full 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 代。

    Why didn't the GC collect? If that wasn't the time to do it, when is?



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

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

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

    I've read a lot that says I shouldn't call GC.Collect() but if GC doesn't collect in this situation, what else can I do?



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

    总而言之,我没有对 CLR 中的自动垃圾收集主题做出公正的评价。我建议通过 msdn 博客文章阅读它(它实际上非常有趣),或者如前所述,Jeffery Richter 的优秀著作 CLR Via C#,第 21 章。

    1 我假设您了解 GC 的 .NET 实现是分代垃圾收集器。用最简单的术语来说,这意味着新创建的对象位于编号较低的代,即第 0 代。当垃圾收集运行时,发现某个代中的对象具有 GC 根(不是“垃圾”),它会被提升到下一代上去。这是一种性能改进,因为 GC 可能需要很长时间并损害性能。这个想法是更高代中的对象通常具有更长的生命周期并且将在应用程序中存在更长时间,因此它不需要像低代那样检查该代是否有垃圾。您可以在 this wikipedia article 中阅读更多信息.您会注意到它也称为临时 GC。

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

    关于c# - 即使在需要时也不会发生垃圾收集,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10016541/

    相关文章:

    c# - CRM 检索实体 - 错误 : Unable to cast object of type 'Microsoft.Xrm.Sdk.Entity' to type 'CRMEntities.List'

    .net - Dapper:找不到方法: 'System.Collections.Generic.IEnumerable` 1&lt;!!0> GridReader.Read(Boolean)'

    c# - Azure SQL Server Rest API 访问

    c++ - 需要释放 QList 内容?

    将数组复制到动态分配的内存

    c# - 下面的场景是如何分配内存的?我设法把它弄糊涂了

    c# - 如何保证异步操作调用的顺序?

    c# - IRC 协议(protocol) - 持续的客户端连接好吗?

    asp.net - 在 Amazon Web Service 中托管 ASP.NET 应用程序

    c++ - C++ OpenMP 代码中的内存泄漏