12

型がガベージ コレクションされないことは .NET ではよく知られています。Reflection.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 によって収集されますか? もしそうなら...ガベージコレクションされたReflection.Emitのケースもありますか?

4

2 に答える 2

20

最初の質問に答えるには:

型のジェネリック構造は収集されません。

ただし、 and を作成するC<string>C<object>、CLR は実際にメソッドのコードを1 回だけ生成します。string への参照と object への参照は同じサイズであることが保証されているため、安全に行うことができます。それはかなり賢いです。構築する場合でもC<int>C<double>メソッドのコードは構築ごとに 1 回ずつ、2 回生成されます。(もちろん、メソッドのコードがまったく生成されていると仮定します。メソッドはオンデマンドでジッティングされます。それがジッティングと呼ばれる理由です。)

ジェネリック型が収集されないことを示すには、代わりにジェネリック型を作成します

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

C<object>メソッド用に生成されたコードをC<string>共有しますが、それぞれが独自の静的フィールドを取得し、それらのフィールドは永久に存続します。構築する型が多いほど、それらの大きな配列でより多くのメモリがいっぱいになります。

これで、これらの型を収集できない理由がわかりました。将来、誰かがこれらの配列のいずれかのメンバーにアクセスしようとするかどうかを知る方法はありません。最後の配列アクセスがいつになるかわからないため、それらは永久に存続する必要があり、したがって、それを含む型も永久に存続する必要があります。


2 番目の質問に答えるには: 動的に生成されたアセンブリを収集する方法はありますか?

はい。ドキュメントは次のとおりです。

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

于 2013-04-18T16:18:07.723 に答える