是否回收MakeGenericType / generic类型的垃圾?

本文关键字:类型 generic MakeGenericType 是否 | 更新日期: 2023-09-27 18:09:58

众所周知,在。net中,类型是不会被垃圾收集的,这意味着如果你在使用fex。反射。Emit,你必须小心卸载AppDomains等等……至少我过去是这样理解事物的。

这让我想知道泛型类型是否被垃圾收集,更准确地说:用MakeGenericType创建的泛型,让我们说…例如基于用户输入。: -)

所以我构造了下面的测试用例:
public interface IRecursiveClass
{
    int Calculate();
}
public class RecursiveClass1<T> : IRecursiveClass 
                                  where T : IRecursiveClass,new()
{
    public int Calculate()
    {
        return new T().Calculate() + 1;
    }
}
public class RecursiveClass2<T> : IRecursiveClass
                                  where T : IRecursiveClass,new()
{
    public int Calculate()
    {
        return new T().Calculate() + 2;
    }
}
public class TailClass : IRecursiveClass
{
    public int Calculate()
    {
        return 0;
    }
}
class RecursiveGenericsTest
{
    public static int CalculateFromUserInput(string str)
    {
        Type tail = typeof(TailClass);
        foreach (char c in str)
        {
            if (c == 0)
            {
                tail = typeof(RecursiveClass1<>).MakeGenericType(tail);
            }
            else
            {
                tail = typeof(RecursiveClass2<>).MakeGenericType(tail);
            }
        }
        IRecursiveClass cl = (IRecursiveClass)Activator.CreateInstance(tail);
        return cl.Calculate();
    }
    static long MemoryUsage
    {
        get
        {
            GC.Collect(GC.MaxGeneration);
            GC.WaitForFullGCComplete();
            return GC.GetTotalMemory(true);
        }
    }
    static void Main(string[] args)
    {
        long start = MemoryUsage;
        int total = 0;
        for (int i = 0; i < 1000000; ++i)
        {
            StringBuilder sb = new StringBuilder();
            int j = i;
            for (int k = 0; k < 20; ++k) // fix the recursion depth
            {
                if ((j & 1) == 1)
                {
                    sb.Append('1');
                }
                else
                {
                    sb.Append('0');
                }
                j >>= 1;
            }
            total += CalculateFromUserInput(sb.ToString());
            if ((i % 10000) == 0)
            {
                Console.WriteLine("Current memory usage @ {0}: {1}", 
                                  i, MemoryUsage - start);
            }
        }
        Console.WriteLine("Done and the total is {0}", total);
        Console.WriteLine("Current memory usage: {0}", MemoryUsage - start);
        Console.ReadLine();
    }
}
如你所见,泛型类型被定义为"可能递归的",带有一个"尾"类,标志着递归的结束。为了确保GC.TotalMemoryUsage没有作弊,我还打开了任务管理器。

到目前为止一切顺利。接下来我做的就是启动这只野兽,当我在等待"内存不足"的时候……我注意到——与我的预期相反——不是随着时间的推移消耗更多的内存。实际上,它显示了内存消耗在时间上的轻微下降。

有人能解释一下吗?泛型类型实际上是由GC收集的吗?如果是这样的话……也有反射。排放被垃圾收集的案例?

是否回收MakeGenericType / generic类型的垃圾?

回答第一个问题:

不收集类型的泛型结构。

但是,如果构造C<string>C<object>, CLR实际上只生成一次方法的代码;由于对string的引用和对object的引用保证具有相同的大小,因此可以安全地这样做。这很聪明。但是,如果构造C<int>C<double>,则方法的代码将生成两次,每次构造一次。(当然,假设方法的代码是生成的;方法是按需修改的;这就是为什么它被称为jiting

要演示不收集泛型类型,而是创建泛型类型

class C<T> { public static readonly T Big = new T[10000]; }

C<object>C<string>共享为方法生成的任何代码,但每个方法都有自己的静态字段,并且这些字段将永远存在。构造的类型越多,这些大数组占用的内存就越多。

现在你知道为什么这些类型不能被收集了;我们无法知道将来是否有人会试图访问这些数组中的成员。因为我们不知道最后一次数组访问是什么时候,所以它们必须永远有效,因此包含它的类型也必须永远有效。


回答你的第二个问题:是否有一种方法可以使动态发出的程序集被收集?

是的。文档在这里:

http://msdn.microsoft.com/en-us/library/dd554932.aspx

与代码共享或不共享无关,每次MakeGenericType尝试都会为元数据创建新的内部CLR类,这将消耗内存。Type对象是直接在CLR代码中创建的(而不是在托管代码中),每个Type对象只存在一个实例,因此您可以比较它们的引用是否相等。CLR本身持有对它的引用,因此它们不能被GC化,但在我的测试中,我确认GC可以移动它们。

编辑: CLR持有的引用可能是弱引用,所以在挖掘RuntimeTypeHandle.cs源代码后,我看到

internal bool IsCollectible()
{
    return RuntimeTypeHandle.IsCollectible(GetTypeHandleInternal());
}

很可能是错的,考虑到Eric Lippert

相关文章: