为什么GC在递归函数中似乎失败?

3 .net c# recursion garbage-collection

如果你按原样运行它会很快执行而不会占用内存.如果你取消注释不好,它会变慢并最终锁定,如果没有内存异常.

为什么GC在递归函数中似乎失败?

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

namespace Repo
{
    class Program
    {
        static Random rng = new Random(42);
        static void Main(string[] args)
        {
            new Thread(Main2, 50 * 1024 * 1024).Start(); //Increase stack size
        }
        static void Main2()
        {
            //Bad(0, "test");
            var ls = Good(0, "test");
            while(ls.Any())
            {
                var v = ls.First();
                ls.AddRange(Good(v.Item1, v.Item2));
                ls.RemoveAt(0);
            }
        }
        class Foo
        {
            public byte[] data;
            public Foo(int size) { data = new byte[size]; }
        }
        static List<Tuple<int, string>> Good(int a, string b)
        {
            if (a >= 5000000)
                return new List<Tuple<int, string>>();
            Console.WriteLine("{0}", a);
            var ls = new List<Tuple<int, string>>();
            {
                var data = new byte[rng.Next(1024, 1024 * 20)]; //This line eats up all the memory
                ls.Add(Tuple.Create(a + 1, ASCIIEncoding.Default.GetString(data, 128, 64)));
            }
            return ls;
        }
        static void Bad(int a, string b)
        {
            if (a >= 5000000)
                return;
            Console.WriteLine("{0}", a);
            var ls = new List<Tuple<int, string>>();
            {
                var data = new byte[rng.Next(1024, 1024 * 20)]; //This line eats up all the memory
                ls.Add(Tuple.Create(a+1, ASCIIEncoding.Default.GetString(data, 128, 64)));
            }
            foreach(var v in ls)
            {
                Bad(v.Item1, v.Item2);
            }
            return;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Eri*_* J. 5

Good()版本中,您分配ls一次,向其添加一个相当小的元组,并Good()返回给调用者.

在该Bad()版本中,初始调用会Bad()产生许多递归调用Bad(),这反过来可以产生许多自己的递归调用.您将继续ls在每次迭代中创建新实例Bad(),并继续添加新元组.您的内存分析器应该向您显示List<Tuple<int, string>>Bad()案例中的类型使用了更多字节.它不是byte[] data,它应该超出范围.

ls给定的递归调用完成之前,无法收集引用.


zin*_*lon 5

JIT可以执行生命周期分析并确定可以在超出范围之前收集局部变量,因此不是您的数组必须以其堆栈帧为根.

但是,在调试版本或调试器下运行时,GC更加保守(如果您想在调试会话期间检查值).在调试器外部运行发布版本,您将看到更加渐进的内存增加,可以通过对列表的生成引用和递归调用中的元组来解释.