是否回收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收集的吗?如果是这样的话……也有反射。排放被垃圾收集的案例?
回答第一个问题:
不收集类型的泛型结构。
但是,如果构造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