6

明示的に呼び出されなかったGC場合に収集されないファイナライズ可能なオブジェクトで問題が発生しました。オブジェクトが を実装している場合は明示的Dispose()に呼び出す必要があることはわかっていますが、フレームワークに依存することは安全であり、オブジェクトが参照されなくなったときに収集できると常に考えていました。Dispose()IDisposable

しかし、windbg/sos/sosex でいくつかの実験を行った結果、ファイナライズ可能なオブジェクトに対してGC.SuppressFinalize()が呼び出されなかった場合、ルート化されていなくても収集されないことがわかりました。そのため、ファイナライズ可能なオブジェクト (DbConnection、FileStream など) を広範囲に使用し、それらを明示的に破棄しないと、メモリ消費量が多すぎたり、OutOfMemoryException.

サンプル アプリケーションは次のとおりです。

public class MemoryTest
{
    private HundredMegabyte hundred;

    public void Run()
    {
        Console.WriteLine("ready to attach");
        for (var i = 0; i < 100; i++)
        {
            Console.WriteLine("iteration #{0}", i + 1);
            hundred = new HundredMegabyte();
            Console.WriteLine("{0} object was initialized", hundred);
            Console.ReadKey();
            //hundred.Dispose();
            hundred = null;
        }
    }

    static void Main()
    {
        var test = new MemoryTest();
        test.Run();
    }
}

public class HundredMegabyte : IDisposable
{
    private readonly Megabyte[] megabytes = new Megabyte[100];

    public HundredMegabyte()
    {
        for (var i = 0; i < megabytes.Length; i++)
        {
            megabytes[i] = new Megabyte();
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~HundredMegabyte()
    {
        Dispose(false);
    }

    private void Dispose(bool disposing)
    {
    }

    public override string ToString()
    {
        return String.Format("{0}MB", megabytes.Length);
    }
}

public class Megabyte
{
    private readonly Kilobyte[] kilobytes = new Kilobyte[1024];

    public Megabyte()
    {
        for (var i = 0; i < kilobytes.Length; i++)
        {
            kilobytes[i] = new Kilobyte();
        }
    }
}

public class Kilobyte
{
    private byte[] bytes = new byte[1024];
}

10回の反復の後でも、メモリ消費量が高すぎて(700MBから1GBに)、反復回数が増えるとさらに高くなることがわかります。WinDBG を使用してプロセスにアタッチすると、すべての大きなオブジェクトがルート化されていませんが、収集されていないことがわかります。

明示的に呼び出すと状況が変わりますSuppressFinalize()。メモリ消費量は高圧下でも 300 ~ 400MB 前後で安定しており、WinDBG はルート化されていないオブジェクトがなく、メモリが空いていることを示しています。

質問は次のとおりです。これはフレームワークのバグですか? 論理的な説明はありますか?

詳細:

各反復の後、windbg は次のことを示します。

  • ファイナライズ キューが空です
  • freachable キューが空です
  • 第 2 世代には、前の反復からのオブジェクト (100 個) が含まれています
  • 以前の反復からのオブジェクトはルート化されていません
4

2 に答える 2

7

ファイナライザーを持つオブジェクトは、ファイナライザーを持たないオブジェクトと同じようには動作しません。

GC が発生し、SuppressFinalize が呼び出されていない場合、GC は Finalizer を実行する必要があるため、インスタンスを収集できません。したがって、ファイナライザーが実行され、かつオブジェクト インスタンスがジェネレーション 1 (最初の GC を生き残ったオブジェクト) に昇格されます。

ジェネレーション 1 (および Gen2) オブジェクトは長寿命と見なされ、Gen1 GC が十分なメモリを解放するのに十分でない場合にのみ、ガベージ コレクションの対象と見なされます。テスト中は常に Gen1 GC で十分だと思います。

この動作は、いくつかの世代を持つことによってもたらされる最適化を無効にするため、GC パフォーマンスに影響を与えます ( gen1 に短い期間のオブジェクトがあります)。

基本的に、ファイナライザーがあり、GC がそれを呼び出すのを防ぐことができないと、既に死んでいるオブジェクトが常に有効期間の長いヒープに昇格されます。これは良いことではありません。

したがって、IDisposable オブジェクトを適切に破棄し、必要でない場合はファイナライザーを回避する必要があります (必要に応じて、IDisposable を実装して GC.SuppressFinalize を呼び出します)。

編集: コード例を十分に読んでいませんでした: あなたのデータはラージ オブジェクト ヒープ (LOH) に存在することを意図しているように見えますが、実際にはそうではありません:ツリーの終わり バイトの小さな配列。

短期間のオブジェクトを LOH に配置すると、コンパクト化されないため、さらに悪化します...したがって、CLR が保持するのに十分な長さの空のメモリ セグメントを見つけられない場合は、多くの空きメモリで OutOfMemory を実行できます。大量のデータ。

于 2012-10-20T19:17:18.033 に答える
1

この背後にある考え方は、IDisposable を実装すると、アンマネージ リソースを処理していて、リソースを手動で破棄する必要があるためだと思います。

GC が Dispose を呼び出すか、それを取り除こうとすると、管理されていないものもフラッシュされます。これは、他の場所で非常によく使用できますが、GC はそれを知る方法がありません。

GC がルート化されていないオブジェクトを削除すると、管理されていないリソースへの参照が失われ、メモリ リークが発生します。

だから... あなたは管理されているか、管理されていません。GC が破棄されていない IDisposables を処理する良い方法はありません。

于 2012-10-20T19:16:54.997 に答える