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

Garbage Collection not happening even when needed(即使在需要时也不会进行垃圾收集)

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

问题描述

我制作了一个 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 在需要时收集,我再次添加.我的电脑开始滑入和滑出磁盘抖动昏迷.为什么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 时,会触发一次完整的 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 代(这是微软实现中的最高代).如果也超出了第 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.

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

本文标题为:即使在需要时也不会进行垃圾收集

基础教程推荐