zoukankan      html  css  js  c++  java
  • 为什么在应该发生垃圾回收的时候却没有发生?

    为什么在应该发生垃圾回收的时候却没有发生?


    此文翻译(有改动)自 StackOverflow 的一个问题和采纳答案,原文链接请点击这里

    问题

    我做了一个 64 位 的 WPF 测试程序。我把程序运行起来并打开任务管理器,同时观察系统内存使用情况。可以看到程序用了 2 GB 的内存,并且还有 6 GB 可用内存。

    在我的程序中,点击一个添加按钮会添加 1 GB 的字节数组到一个列表中,并可以观察到系统内存使用量增加了 1 GB。我总共点击了 6 次添加按钮以将这 6 GB 可用内存全部填满。

    接着我点击了 6 次移除按钮将之前添加的数组从列表中移除。这些被移除的字节数组应该没有被任何对象引用了。

    当我移除后,并没有看见内存使用量下降。但我可以理解这个,因为我知道 GC 并不是立即执行的。我明白 GC 会在将来需要的时候发生的。

    因此现在内存看起来虽然是满的,但 GC 在需要的时候会发生的。我再次点击添加按钮,却发生了 OutOfMemoryException 。为什么 GC 没有发生呢?如果现在还不是回收发生的时机,那什么时候才是呢?

    为了验证,我有一个按钮用于强制 GC 。当我按下这个按钮时,很快就获得了 6 GB 可用内存。这不就证明了我的 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();
    }
    

    采纳答案

    为了验证,我有一个按钮用于强制 GC 。当我按下这个按钮时,很快就获得了 6 GB 可用内存。这不就证明了我的 6 个数组没有被任何对象引用并且可以被 GC 回收吗?

    把你的问题换一种问法:GC 在何时自动回收“垃圾”内存?

    在我的脑海里首先出现的是:

    • 大多数时候,当第 0 代满了或者没有足够的空余空间分配某个对象时会发生垃圾回收
    • 某些时候,当分配一大块内存时可能引发 OutOfMemoryException 异常,一次完整 GC 将会被触发并尝试回收可用内存。如果在回收后仍然没有足够的连续内存,那么 OOM 异常将会被抛出。

    当开始一次垃圾收集时,GC 将会决定哪些代需要被回收(0,0+1,或者所有)。每一代都有一个由 GC 决定的大小(在程序运行时可能会被改变)。如果只有第 0 代超过了容量,那么它就是唯一将被垃圾回收的代。如果从第 0 代垃圾回收中存活下来的对象造成第 1 代超过了容量,那么第 1 代也将被回收,并且存活下来的对象会提升到第 2 代(这是微软实现中最高的代)。如果第 2 代也超过了容量,那么垃圾也会被回收,但是对象不会再提升至更高的代,因为不存在更高的代了。

    因此,这里隐含着很重要的信息,按照 GC 大多数时候运行的方式,第 2 代只会在第 0 代和第 1 代均满了的情况下才会被回收。同时,你还应该知道大于 85000 字节的对象不是存放在标记着第 0,1,2 代的普通 GC 堆里的。它实际上是存放在一个叫做大型对象堆(Large Object HeapLOH)里的。LOH 里的内存只会在发生完整回收时才会被释放(即第 2 代被回收时)。绝不会在第 0 代或者第 1 代回收发生时发生。

    为什么 GC 没有发生呢?如果现在还不是回收发生的时机,那什么时候才是呢?

    现在 GC 为什么没有自动开始应该很明显了。你只在 LOH(记住像你这种使用 int 类型的方式是在栈上分配的内存并且不需要被回收)上创建了对象。你从来没有填充过第 0 代,所以 GC 从来没有发生。

    同时,你在 64 位模式下运行,这意味着你也不会触发我上面列举的其他情况,即当分配某个特定对象时整个应用程序发生内存不足。64 位应用程序拥有 8 TB 的虚拟地址空间,因此你是不大可能满足这种条件的。很大可能是在这种情况发生前已耗尽物理内存和页文件空间。

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

    我读过很多文章都说不应该调用 GC.Collect(),但是如果 GC 在这种情形下不发生,我还能做什么呢?

    如果你的代码必须要这样写,那么请调用 GC.Collect()。但是,最好不要在测试之外写这种代码。

    最后,我没有对 CLR 中的自动垃圾回收下任何结论。我建议通过 MSDN 上的帖子来了解它(它真的非常有趣),或者像其他人已经提及的,通过 Jeffery Richter 写的很棒的书,CLR via C#,第 21 章。

    如果你不相信我,当你移除一个数组后,用一个方法创建一组随机字符串或者对象(我建议在这个测试中使用原生类型的数组),当创建的对象达到一定数量时,将会发生一次完整 GC 将你分配在 LOH 中的内存释放。

  • 相关阅读:
    鳥哥的 Linux 私房菜——第十三章、学习 Shell Scripts(转发)(未完待续)
    鳥哥的 Linux 私房菜——第十六章、例行性工作排程 (crontab) (转发)(未完待续)
    RT-Thread ------ event 事件
    sscanf() ------ 获取字符串中的参数
    燃气热水器的调节
    Adobe Illustrator CC ------ AI脚本插件合集
    你真的理解CSS的linear-gradient?
    IDEA中Grep Console插件的安装及使用
    Windows下删除以.结尾文件夹的方法
    lwip库的发送和接收函数
  • 原文地址:https://www.cnblogs.com/platobeing/p/3908442.html
Copyright © 2011-2022 走看看