21

AppDomain.Unload(myDomain) を実行すると、完全なガベージ コレクションも実行されると予想されます。

「C# による CLR」の Jeffrey Richter によると、彼は AppDomain.Unload 中に次のように述べています。

CLR はガベージ コレクションを強制的に発生させ、現在アンロードされている AppDomain によって作成されたオブジェクトによって使用されていたメモリを再利用します。これらのオブジェクトの Finalize メソッドが呼び出され、オブジェクトが適切にクリーンアップされる機会が与えられます。

「.NET Framework 共通言語ランタイムのカスタマイズ」の「Steven Pratschner」によると:

すべてのファイナライザーが実行され、ドメインで実行中のスレッドがなくなると、CLR は内部実装で使用されるすべてのメモリ内データ構造をアンロードする準備が整います。ただし、これが発生する前に、ドメインに存在するオブジェクトを収集する必要があります。次のガベージ コレクションが発生した後、アプリケーション ドメインのデータ構造はプロセス アドレス空間からアンロードされ、ドメインはアンロードされたと見なされます。

私は彼らの言葉を誤解していますか?予期しない動作を再現するために次の解決策を実行しました(.net 2.0 sp2で):

このインターフェイスを含む「Interfaces」という名前のクラス ライブラリ プロジェクト:

   public interface IXmlClass
    {
        void AllocateMemory(int size);

        void Collect();
    }

「Interfaces」を参照し、次のクラスを含む「ClassLibrary1」という名前のクラス ライブラリ プロジェクト:

public class XmlClass : MarshalByRefObject, IXmlClass
{

    private byte[] b;

    public void AllocateMemory(int size)
    {
        this.b = new byte[size];
    }

    public void Collect()
    {
        Console.WriteLine("Call explicit GC.Collect() in " + AppDomain.CurrentDomain.FriendlyName + " Collect() method");
        GC.Collect();
        Console.WriteLine("Number of collections: Gen0:{0} Gen1:{1} Gen2:{2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2));
    }

    ~XmlClass()
    {
        Console.WriteLine("Finalizing in AppDomain {0}", AppDomain.CurrentDomain.FriendlyName);
    }
}

「Interfaces」プロジェクトを参照し、次のロジックを実行するコンソール アプリケーション プロジェクト:

static void Main(string[] args)
{
    AssemblyName an = AssemblyName.GetAssemblyName("ClassLibrary1.dll");
    AppDomain appDomain2 = AppDomain.CreateDomain("MyDomain", null, AppDomain.CurrentDomain.SetupInformation);
    IXmlClass c1 = (IXmlClass)appDomain2.CreateInstanceAndUnwrap(an.FullName, "ClassLibrary1.XmlClass");
    Console.WriteLine("Loaded Domain {0}", appDomain2.FriendlyName);
    int tenmb = 1024 * 10000;
    c1.AllocateMemory(tenmb);
    Console.WriteLine("Number of collections: Gen0:{0} Gen1:{1} Gen2:{2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2));
    c1.Collect();
    Console.WriteLine("Unloaded Domain{0}", appDomain2.FriendlyName);
    AppDomain.Unload(appDomain2);
    Console.WriteLine("Number of collections after unloading appdomain:  Gen0:{0} Gen1:{1} Gen2:{2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2));
    Console.WriteLine("Perform explicit GC.Collect() in Default Domain");
    GC.Collect();
    Console.WriteLine("Number of collections: Gen0:{0} Gen1:{1} Gen2:{2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2));
    Console.ReadKey();
}

コンソール アプリケーションを実行したときの出力は次のとおりです。

Loaded Domain MyDomain
Number of collections: Gen0:0 Gen1:0 Gen2:0
Call explicit GC.Collect() in MyDomain Collect() method
Number of collections: Gen0:1 Gen1:1 Gen2:1
Unloaded Domain MyDomain
Finalizing in AppDomain MyDomain
Number of collections after unloading appdomain:  Gen0:1 Gen1:1 Gen2:1
Perform explicit GC.Collect() in Default Domain
Number of collections: Gen0:2 Gen1:2 Gen2:2

注意事項:

  1. ガベージ コレクションはプロセスごとに行われます (単なる復習)

  2. アンロードされる appdomain 内のオブジェクトにはファイナライザーが呼び出されますが、ガベージ コレクションは行われません。AllocateMemory() によって作成された 10 メガバイトのオブジェクトは、上記の例で明示的な GC.Collect() を実行した後にのみ収集されます (またはガベージ コレクターが後で収集する場合)。

その他の注意事項: XmlClass がファイナライズ可能かどうかは問題ではありません。上記の例でも同じ動作が発生します。

質問:

  1. AppDomain.Unload を呼び出してもガベージ コレクションが発生しないのはなぜですか? その呼び出しをガベージ コレクションにする方法はありますか?

  2. AllocateMemory() 内で、LargeObject ヒープ上に生成され、第 2 世代のオブジェクトとなる短期間の大きな xml ドキュメント (16 MB 以下) をロードする予定です。明示的な GC.Collect() またはガベージ コレクターの他の種類の明示的なプログラムによる制御に頼らずにメモリを収集する方法はありますか?

4

2 に答える 2

20

その他の注意事項:

親切にも質問を見てくれたジェフリー・リヒターとのメール交換の後:

OK、あなたの投稿を読みました。
まず、配列は XMLClass オブジェクトが GC されるまで GC されません。このオブジェクトには Finalize メソッドが含まれているため、このオブジェクトを収集するには 2 つの GC が必要です。
第 2 に、appdomain をアンロードすると、少なくとも GC のマーキング フェーズが実行されます。これは、Finalize メソッドを呼び出すことができるように、どのオブジェクトが到達不能であるかを判断する唯一の方法であるためです。
ただし、GC のアンロード時に GC の圧縮部分が実行される場合と実行されない場合があります。GC.CollectionCount を明白に呼び出しても、すべてがわかるわけではありません。GC マーキング フェーズが発生したことを示しているわけではありません。
また、AppDomain.Unload が内部コードを介して GC を開始し、コレクション カウント変数がインクリメントされない可能性があります。マーキング フェーズが実行されており、コレクション カウントがこれを反映していないことは既にわかっています。

より良いテストは、デバッガーでいくつかのオブジェクト アドレスを調べて、圧縮が実際に発生するかどうかを確認することです。もしそうなら(そして私はそう思う)、コレクション数が正しく更新されていないだけです。

これを私の回答として Web サイトに投稿したい場合は、投稿してください。

彼のアドバイスを受けてSOSを調べた後(ファイナライザーも削除しました)、次のことが明らかになりました。

AppDomain.Unload の前:

!EEHeap -gc
Number of GC Heaps: 1
generation 0 starts at 0x0180b1f0
generation 1 starts at 0x017d100c
generation 2 starts at 0x017d1000
ephemeral segment allocation context: none
 segment    begin allocated     size
017d0000 017d1000  01811ff4 0x00040ff4(266228)
Large object heap starts at 0x027d1000
 segment    begin allocated     size
027d0000 027d1000  02f75470 0x007a4470(8012912)
Total Size  0x7e5464(8279140)
------------------------------
GC Heap Size  0x7e5464(8279140)

AppDomain.Unload の後 (同じアドレス、ヒープ圧縮は行われませんでした)

!EEHeap -gc
Number of GC Heaps: 1
generation 0 starts at 0x0180b1f0
generation 1 starts at 0x017d100c
generation 2 starts at 0x017d1000
ephemeral segment allocation context: none
 segment    begin allocated     size
017d0000 017d1000  01811ff4 0x00040ff4(266228)
Large object heap starts at 0x027d1000
 segment    begin allocated     size
027d0000 027d1000  02f75470 0x007a4470(8012912)
Total Size  0x7e5464(8279140)
------------------------------
GC Heap Size  0x7e5464(8279140)

GC.Collect() の後、ヒープ圧縮が行われたことを示すアドレスが異なります。

!EEHeap -gc
Number of GC Heaps: 1
generation 0 starts at 0x01811234
generation 1 starts at 0x0180b1f0
generation 2 starts at 0x017d1000
ephemeral segment allocation context: none
 segment    begin allocated     size
017d0000 017d1000  01811ff4 0x00040ff4(266228)
Large object heap starts at 0x027d1000
 segment    begin allocated     size
027d0000 027d1000  027d3240 0x00002240(8768)
Total Size   0x43234(274996)
------------------------------
GC Heap Size   0x43234(274996)

さらにsosした後、私が到達した結論は、それは確かに設計によるものであり、ヒープの圧縮は必ずしも行われないということです。AppDomain のアンロード中に本当に確実にできる唯一のことは、オブジェクトが到達不能としてマークされ、次のガベージ コレクション中に収集されることです (これは、アプリケーション ドメインをアンロードするときに正確に行われるわけではありません。偶然です)。

編集: GC チームで直接働いている Maoni Stephens にも尋ねました。ここのコメントのどこかで彼女の反応を読むことができます。彼女はそれが設計によるものであることを確認します。ケースクローズ:)

于 2010-04-29T07:19:57.693 に答える
5
  1. おそらく仕様によるものですが、なぜこの動作が必要なのかわかりません (明示的な GC.Collect)。ファイナライザーが呼び出される限り、オブジェクトはファイナライザー キューから削除され、必要に応じてガベージ コレクションの準備が整います (必要に応じて gc スレッドが開始されます)。

  2. おそらく、厄介なアンマネージ割り当てと重い相互運用を使用するか、アンマネージ C++ でコーディングしてから、マネージ ラッパーを使用して C# 経由でアクセスできますが、マネージ .Net の世界にとどまっている限り、できません。

    ガベージ コレクターの役割を果たそうとすることに集中するのではなく、アーキテクチャを再検討する方が賢明です。

于 2010-04-27T18:05:08.313 に答える