代码之家  ›  专栏  ›  技术社区  ›  The Fiddler

这是怎么回事?(.NET)gc.CollectionCount(0)不断增加

  •  5
  • The Fiddler  · 技术社区  · 14 年前

    在测试应用程序性能时,我遇到了一些非常奇怪的GC行为。简而言之,GC甚至在没有运行时分配的空程序上运行!

    以下应用程序演示了该问题:

    using System;
    using System.Collections.Generic;
    
    public class Program
    {
        // Preallocate strings to avoid runtime allocations.
        static readonly List<string> Integers = new List<string>();
        static int StartingCollections0, StartingCollections1, StartingCollections2;
    
        static Program()
        {
            for (int i = 0; i < 1000000; i++)
                Integers.Add(i.ToString());
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
        }
    
        static void Main(string[] args)
        {
            DateTime start = DateTime.Now;
            int i = 0;
            Console.WriteLine("Test 1");
            StartingCollections0 = GC.CollectionCount(0);
            StartingCollections1 = GC.CollectionCount(1);
            StartingCollections2 = GC.CollectionCount(2);
            while (true)
            {
                if (++i >= Integers.Count)
                {
                    Console.WriteLine();
                    break;
                }
    
                // 1st test - no collections!
                {
                    if (i % 50000 == 0)
                    {
                        PrintCollections();
                        Console.Write(" - ");
                        Console.WriteLine(Integers[i]);
                        //System.Threading.Thread.Sleep(100);
                        // or a busy wait (run in debug mode)
                        for (int j = 0; j < 50000000; j++)
                        { }
                    }
                }
            }
    
            i = 0;
            Console.WriteLine("Test 2");
            StartingCollections0 = GC.CollectionCount(0);
            StartingCollections1 = GC.CollectionCount(1);
            StartingCollections2 = GC.CollectionCount(2);
            while (true)
            {
                if (++i >= Integers.Count)
                {
                    Console.WriteLine("Press any key to continue...");
                    Console.ReadKey(true);
                    return;
                }
    
                DateTime now = DateTime.Now;
                TimeSpan span = now.Subtract(start);
                double seconds = span.TotalSeconds;
    
                // 2nd test - several collections
                if (seconds >= 0.1)
                {
                    PrintCollections();
                    Console.Write(" - ");
                    Console.WriteLine(Integers[i]);
                    start = now;
                }
            }
        }
    
        static void PrintCollections()
        {
            Console.Write(Integers[GC.CollectionCount(0) - StartingCollections0]);
            Console.Write("|");
            Console.Write(Integers[GC.CollectionCount(1) - StartingCollections1]);
            Console.Write("|");
            Console.Write(Integers[GC.CollectionCount(2) - StartingCollections2]);
        }
    }
    

    有人能解释一下这是怎么回事吗?我的印象是,除非内存压力达到特定的限制,否则GC不会运行。然而,它似乎一直在运行(收集)-这是正常的吗?

    编辑:我修改了程序以避免所有运行时分配。

    编辑2:好的,新的迭代,似乎是日期时间的罪魁祸首。其中一个datetime方法分配内存(可能是减法),这会导致GC运行。第一次测试现在绝对会导致 集合-如预期的那样-而第二个则导致多个。

    简而言之,GC只在需要运行时运行—我只是无意中生成了内存压力(datetime是一个结构,我认为它不会生成垃圾)。

    4 回复  |  直到 10 年前
        1
  •  5
  •   stapeluberlauf    14 年前

    GC.CollectionCount(0) 返回以下内容:

    自进程启动以来,为指定生成发生垃圾收集的次数。

    因此,您应该看到数字的增加,这并不意味着内存在泄漏,而是GC已经运行。

    在第一种情况下,您也可以看到这种增长。它只会发生得慢得多,因为非常慢 Console.WriteLine 方法的调用频率更高,大大降低了速度。

        2
  •  3
  •   Liam Joshua    10 年前

    另一个需要注意的是 GC.Collect() 同步函数调用。它会触发垃圾收集,但垃圾收集发生在后台线程上,理论上,在您检查您的 GC 统计数据。

    有一个 GC.WaitForPendingFinalizers 你可以打电话 GC.Collect 阻止,直到垃圾收集发生。

    如果您真的想在不同的情况下准确地跟踪GC统计信息,我将在您的进程中使用Windows性能监视器,您可以在其中创建各种类型的监视器,包括.NET堆统计信息。

        3
  •  1
  •   Guffa    14 年前

    如果您只等待几秒钟,您会看到收集计数在第一个测试中也会增加,但不会增加得那么快。

    代码之间的区别在于,第一个测试总是尽可能快地写出收集计数,而第二个测试循环,直到达到时间限制才写出任何数据。

    第一个测试花费大部分时间等待文本写入控制台,而第二个测试花费大部分时间循环,等待时间限制。第二个测试将在同一时间进行更多的迭代。

    我计算了迭代次数,并打印出每个垃圾收集的迭代次数。在我的计算机上,第一个测试稳定在每个GC 45000次迭代左右,而第二个测试稳定在每个GC 130000次迭代左右。

    所以,第一个测试实际上是 更多 垃圾收集比第二次测试多了大约三倍。

        4
  •  0
  •   Liam Joshua    10 年前

    谢谢大家!你的建议有助于揭露罪魁祸首: DateTime 正在分配堆内存。

    GC不会一直运行,但只在分配内存时运行。如果内存使用率很低,则 GC 永远不会跑 GC.CollectionCount(0) 会一直回来的 0 ,如预期。

    测试的最新迭代展示了这种行为。第一次测试运行没有分配任何堆内存( gc.CollectionCount(0) 残余 ,而第二个则以一种不明显的方式分配内存:通过 DateTime.Subtract() -gt; Timespan .

    现在,两个 日期时间 时间跨度 是值类型,这就是为什么我发现这种行为令人惊讶的原因。不过,你已经知道了:毕竟有一个内存泄漏。