即使在需要时也不会发生垃圾收集 [英] Garbage Collection not happening even when needed

查看:18
本文介绍了即使在需要时也不会发生垃圾收集的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I made a 64-bit WPF test app. With my app running and with Task Manager open, I watch my system memory usage. I see I'm using 2GB, and I have 6GB available.

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

In my app, I click an Add button to add a new 1GB byte array to a list. I see my system memory usage increases by 1GB. I click Add a total of 6 times, filling the 6GB of memory I had available when I started.

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

I click a Remove button 6 times to remove each array from the list. The removed byte arrays should not be referenced by any other object in my control.

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

When I Remove, I don't see my memory go down. But that's OK with me, because I understand that GC is non-deterministic and all that. I figure the GC WILL collect as needed.

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

So now with memory looking full, but expecting the GC to collect when needed, I Add again. My PC starts slipping in and out of a disk thrashing coma. Why didn't the GC collect? If that wasn't the time to do it, when is?

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

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?

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

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?

    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();
    }

推荐答案

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

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?

您最好询问GC 何时自动收集垃圾"内存?.我的头顶:

You are probably better off asking When does the GC automatically collect "garbage" memory?. Off the top of my head:

  • 最常见的是,当第 0 代已满或对象分配不适合可用的可用空间时.1
  • 有些普遍的情况是,当分配一块内存会导致 OutOfMemoryException 时,会触发 full GC 以首先尝试回收可用内存.如果收集后没有足够的连续内存可用,则会抛出 OOM 异常.
  • Most commonly, when generation 0 is full or an object allocation won't fit in the available free space.1
  • Somewhat commonly, when allocating a chunk of memory would cause an OutOfMemoryException, a full GC is triggered to first try and reclaim available memory. If not enough contiguous memory is available after the collection, then an OOM exception will be thrown.

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

When starting a garbage collection, the GC determines what generations need to be collected (0, 0+1, or all). Each generation has a size determined by the GC (it can change as the application runs). If only generation 0 will exceed its budget, that is the only generation whose garbage will be collected. If the objects that survive generation 0 will cause generation 1 to go over its budget, then generation 1 will also be collected and its surviving objects will be promoted to generation 2 (which is the highest generation in Microsoft's implementation). If the budget for generation 2 is exceeded as well, garbage will be collected, but objects can't be promoted to a higher generation, as one doesn't exist.

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

So, here lies the important information, in the most common way the GC is started, Generation 2 will only be collected if generation 0 and generation 1 are both full. Also, you need to know that objects over 85,000 bytes are not stored in the normal GC heap with generation 0, 1, and 2. It's actually stored in what is called the Large Object Heap (LOH). The memory in the LOH is only freed during a FULL collection (that is, when generation 2 is collected); never when only generations 0 or 1 are being collected.

为什么GC没有收集?如果那不是这样做的时候,那是什么时候?

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

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

It should now be obvious why the GC never happened automatically. You're only creating objects on the LOH (keep in mind that int types, the way you've used them, are allocated on the stack and don't have to be collected). You are never filling up generation 0, so a GC never happens.1

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

You're also running it in 64-bit mode, which means it's unlikely you'll hit the other case I listed above, where a collection occurs when there's not enough memory in the entire application to allocate a certain object. 64-bit applications have a virtual address space limit of 8TB so it would be a while before you hit this case. It's more likely you'll run out of physical memory and page file space before that happens.

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

Since a GC hasn't happened, windows starts to allocate memory for your application from the available space in the page file.

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

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().更好的是,不要在测试之外编写此类代码.

Call GC.Collect() if this kind of code is what you need to write. Better yet, don't write this kind of code outside of testing.

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

In conclusion, I have not done justice to the topic of automatic garbage collection in the CLR. I recommend reading about it (it's actually very interesting) via msdn blog posts, or as has already been mentioned, Jeffery Richter's excellent book, CLR Via C#, Chapter 21.

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

1 I'm making the assumption that you understand that the .NET implementation of the GC is a generational garbage collector. In the simplest terms, it means that newly created objects are in a lower numbered generation, i.e. generation 0. As garbage collections are run and it's found that an object that is in a certain generation has a GC root (not "garbage"), it will be promoted to the next generation up. This is a performance improvement since GC can take a long time and hurt performance. The idea is that objects in higher generations generally have a longer life and will be around in the application longer, so it doesn't need to check that generation for garbage as much as the lower generations. You can read more in this wikipedia article. You'll notice it's also called a ephemeral GC.

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

2 If you don't believe me, after you remove one of the chunks, have a function that creates a whole bunch of random strings or objects (I'd recommend against arrays of primitives for this test) and you'll see after you reach a certain amount of space, a full GC will occur, freeing that memory you had allocated in the LOH.

这篇关于即使在需要时也不会发生垃圾收集的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆