这里发生了什么?(.Net)GC.CollectionCount(0)不断增加

The*_*ler 5 .net c# mono garbage-collection memory-leaks

在测试应用程序性能时,我遇到了一些非常奇怪的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]);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

编辑2:好的,新的迭代,似乎DateTime是罪魁祸首.其中一个DateTime方法分配内存(可能是Subtract),这会导致GC运行.第一个测试现在完全没有收集 - 正如预期的那样 - 而第二个导致几个收集.

简而言之,GC只在需要运行时运行 - 我只是在不知不觉中产生内存压力(DateTime是一个结构,我认为它不会产生垃圾).

sta*_*auf 5

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

自进程启动以来指定生成的垃圾收集次数.

因此,您应该看到数量增加,并且这种增加并不意味着内存泄漏但GC已经运行.

同样在第一种情况下,您可以看到这种增加.它会发生得慢得多,因为非常慢的Console.WriteLine方法被更频繁地调用,减慢了很多东西.


The*_*ler 0

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

GC 不会一直运行,而是仅在分配内存时运行。如果内存使用量持平,则将GC永远不会运行,并且GC.CollectionCount(0)将始终返回0,如预期的那样。

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

现在, 和DateTime都是Timespan值类型,这就是为什么我发现这种行为令人惊讶。尽管如此,你还是明白了:毕竟存在内存泄漏。