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

しかし、私は、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回の反復の後、あなたはそのメモリ消費量は、(700メガバイトから1ギガバイトまで)が高すぎると、より多くの反復でさえ高くなる見つけることができます。 WinDBGを使用してプロセスにアタッチすると、すべてのラージオブジェクトはルーティングされていませんが、収集されません。

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

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

詳細:ことを各反復後

、windbgのに示す:ファイナライズキューが空である

  • freachableキューは
  • 世代2は、前の反復からのオブジェクト(百)が含ま空である
  • 以前の反復のオブジェクトは、ループされていません
+0

私はそれを実行しようとしたが、私はそれを100mb程度まで削減した時点で700mbに達した。 (私はちょうどリターンを押し続け、タスクマネージャのメモリ使用量を見ていました)。 Windows 8を実行中。 – keyboardP

+0

非常に興味深い...私はwin7 x64でテストしました。 1秒間停止して最初の10回の繰り返しをした後に、鍵を保持しています... outofmemory – 6opuc

+0

もう一度試しましたが、例外なく100回繰り返しました。また、64ビット、かなり面白いです。たぶん誰かがテストして結果を投稿することもできます。 – keyboardP

答えて

7

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

GCが発生し、SuppressFinalizeが呼び出されていない場合、GCはファイナライザを実行する必要があるため、インスタンスを収集できません。したがって、ファイナライザが実行され、すでにリビング参照がなくても、オブジェクトインスタンスは世代1(最初のGCで生き残ったオブジェクト)に昇格されます。

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

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

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

あなたはそのため正しくIDisposableをオブジェクトを配置し、必要に応じていない場合はファイナライザを避ける必要があります(必要に応じて、IDisposableインターを実装し、GC.SuppressFinalizeを呼び出します。)

編集: 私はよく、コードの例を読んでいません十分に:あなたのデータは、ラージオブジェクトヒープ(LOH)に存在するように見えますが、実際にはそうではありません。ツリーの最後に小さなバイト配列を含む小さな配列の参照がたくさんあります。

短時間のオブジェクトをLOHに入れても圧縮されないため、ひどいです...したがって、CLRが空のメモリセグメントを見つけることができない場合は、空きメモリが多いOutOfMemoryを実行できます大きなデータを格納するのに十分な長さです。

+0

+1私はすべてのステートメントに同意するが、誰かが同じ問題に遭遇したときに待っていたい。このサンプル・アプリケーションは、ストリームやsqlコマンドのDispose()を使用していないフレームワーク(EF、spring.net、log4net)の一部が使用されている生産環境の単純化されたバージョンです。 – 6opuc

+0

私はあなたの提案をチェックしましたファイナライザがコメントアウトされても、ほとんどのオブジェクトが第2世代に割り当てられる(または移動される)ことがわかりました。だから2世代は間違いなくこの問題の原因ではありません。 – 6opuc

+0

GCを起動してもよろしいですか?メモリが不足していない限り、CLRはトリガしません。 GC.Collect()を呼び出して、実行されていることを確認してください。 – Eilistraee

1

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

GCがDisposeを呼び出すか、それを取り除こうとすると、アンマネージドなものもフラッシュされます。これは他の場所でもうまく使用できます.GCはこれを知る方法がありません。

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

だからあなたは管理されています。 GCが配置されていないIDisposablesを処理するための良い方法はありません。

関連する問題