有些情况下需要手动调用GC.Collect

时间:2014-03-17 21:50:20

标签: c# .net memory-management memory-leaks garbage-collection

我已经阅读了很多关于GC的文章,并且关于"不关心对象"范式,但我做了一个测试,以证明它。

所以想法是:我创建了许多存储在本地函数中的大对象,我怀疑在完成所有任务后它将清理内存本身。但GC并没有。所以测试代码:

class Program
{
    static void Main()
    {
        var allDone = new ManualResetEvent(false);
        int completed = 0;
        long sum = 0; //just to prevent optimizer to remove cycle etc.
        const int count = int.MaxValue/10000000;
        for (int i = 0; i < count; i++)
        {
            ThreadPool.QueueUserWorkItem(delegate
                                         {
                                             unchecked
                                             {
                                                 var dumb = new Dumb();
                                                 var localSum = 0;
                                                 foreach (int x in dumb.Arr)
                                                 {
                                                     localSum += x;
                                                 }
                                                 sum += localSum;
                                             }
                                             if (Interlocked.Increment(ref completed) == count)
                                                 allDone.Set();
                                             if (completed%(count/100) == 0)
                                                 Console.WriteLine("Progress = {0:N2}%", 100.0*completed/count);
                                         });
        }
        allDone.WaitOne();
        Console.WriteLine("Done. Result : {0}", sum);
        Console.ReadKey();
        GC.Collect();
        Console.WriteLine("GC Collected!");
        Console.WriteLine("GC CollectionsCount 0 = {0}, 1 = {1}, 2 = {2}", GC.CollectionCount(0), GC.CollectionCount(1),GC.CollectionCount(2));
        Console.ReadKey();
    }
}

class Dumb
{
    public int[] Arr = Enumerable.Range(1,10*1024*1024).ToArray(); // 50MB 
}

所以在我的情况下应用程序会占用~2GB的RAM,但是当我点击键盘并启动GC.Collect时,它会释放内存,最大正常大小为20mb。

我读过GC手动调用是不好的做法,但在这种情况下我无法避免。

5 个答案:

答案 0 :(得分:2)

.NET运行时将在不调用GC的情况下进行垃圾回收。但是,GC方法是公开的,因此GC集合可以根据用户体验(加载屏幕,等待下载等)进行计时。

使用GC方法并不总是一个坏主意,但如果你需要问,那么可能就是这样。 :-)

答案 1 :(得分:2)

  

我读过GC手动调用是不好的做法,但在这种情况下我无法避免。

你可以避免它。不要打电话给它。下次您尝试进行分配时,GC可能会启动并为您处理此事。

答案 2 :(得分:2)

在您的示例中,无需明确调用GC.Collect() 如果在任务管理器或性能监视器中将其启动,您将看到GC在运行时正常工作。操作系统在需要时调用GC(当它尝试分配并且没有内存时,它会调用GC以释放一些内存)。

由于你的对象(大于85000字节)进入大对象堆LOH,你需要注意large object heap fragmentation。我修改了你的代码,以便展示你如何分割LOH。即使内存可用,也会产生内存不足的异常,而不是连续的内存。从.NET 4.5.1开始,您可以将标志设置为request that LOH to be compacted

我修改了你的代码,在这里展示了一个例子:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace GCTesting
{
    class Program
    {
        static int fragLOHbyIncrementing = 1000;
        static void Main()
        {
            var allDone = new ManualResetEvent(false);
            int completed = 0;
            long sum = 0; //just to prevent optimizer to remove cycle etc.
            const int count = 2000;
            for (int i = 0; i < count; i++)
            {
                ThreadPool.QueueUserWorkItem(delegate
                {
                    unchecked
                    {
                        var dumb = new Dumb( fragLOHbyIncrementing++ );
                        var localSum = 0;
                        foreach (int x in dumb.Arr)
                        {
                            localSum += x;
                        }
                        sum += localSum;
                    }
                    if (Interlocked.Increment(ref completed) == count)
                        allDone.Set();
                    if (completed % (count / 100) == 0)
                        Console.WriteLine("Progress = {0:N2}%", 100.0 * completed / count);
                });
            }
            allDone.WaitOne();
            Console.WriteLine("Done. Result : {0}", sum);
            Console.ReadKey();
            GC.Collect();
            Console.WriteLine("GC Collected!");
            Console.WriteLine("GC CollectionsCount 0 = {0}, 1 = {1}, 2 = {2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2));
            Console.ReadKey();
        }
    }

    class Dumb
    {
        public Dumb(int incr)
        {
            try
            {
                DumbAllocation(incr);
            } 
            catch (OutOfMemoryException)
            {
                Console.WriteLine("Out of memory, trying to compact the LOH.");

                GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
                GC.Collect();

                try // try again
                {
                    DumbAllocation(incr); 
                    Console.WriteLine("compacting the LOH worked to free up memory.");
                }
                catch (OutOfMemoryException)
                {
                    Console.WriteLine("compaction of LOH failed to free memory.");
                    throw;
                }
            }
        }

        private void DumbAllocation(int incr)
        {
            Arr = Enumerable.Range(1, (10 * 1024 * 1024) + incr).ToArray();
        }

        public int[] Arr;
    }
}

答案 3 :(得分:0)

我能想到的几件事可能会影响到这一点,但没有一件事可以肯定:(

  1. 一个可能的影响是GC不会马上开始......大型物品在收集队列中 - 但还没有被清理干净。特别是在那里调用GC.Collect强制集合,以及那些你看到差异的地方。否则它会在某个时间点发生。
  2. 我能想到的第二个原因是GC可能会收集对象,但不一定会向OS释放内存。因此,即使内存空闲且可供分配,您仍可继续看到高内存使用率。

答案 4 :(得分:0)

垃圾收集很聪明,决定何时收集对象的权利。这是通过启发式方法完成的,您必须阅读相关内容。垃圾收集使他的工作非常好。 2GB对于你的系统是一个问题,还是你只是想知道这个行为?

无论何时调用GC.Collect(),都不要忘记调用GC.WaitingForPendingFinalizer。这样可以避免使用终结器对对象造成不必要的老化。