web-dev-qa-db-ja.com

C#でメモリを解放する正しい方法は何ですか

メソッド内でコードを実行するC#のタイマーがあります。コード内では、いくつかの一時オブジェクトを使用しています。

  1. メソッド内にFoo o = new Foo();のようなものがある場合、それはタイマーが作動するたびに新しいオブジェクトとそのオブジェクトへの新しい参照を作成しているということですか?

  2. _string foo = null_があり、fooに一時的なものを入れた場合、上記と同じですか?

  3. ガベージコレクターはオブジェクトを削除しますが、参照またはオブジェクトは継続的に作成され、メモリに残りますか?

  4. _Foo o;_を宣言して、それをインスタンスに向けない場合、メソッドの終了時に破棄されませんか?

  5. すべてを確実に削除したい場合、それを行う最善の方法は何ですか:

    • メソッド内にusingステートメントを使用
    • 最後にdisposeメソッドを呼び出して
    • _Foo o;_をタイマーのメソッドの外に置き、内部で割り当てo = new Foo()を行うだけで、メソッドの終了後にオブジェクトへのポインターが削除されると、ガベージコレクターはオブジェクトを削除します。
32
user579674

1. Foo o = new Foo();のようなものがある場合メソッド内では、タイマーが作動するたびに、新しいオブジェクトとそのオブジェクトへの新しい参照を作成しているということですか?

はい。

2. string foo = nullがあり、fooに一時的なものを入れるだけの場合、上記と同じですか?

動作が同じかどうかを尋ねている場合は、はい。

3.ガベージコレクターはオブジェクトを削除しますが、参照またはオブジェクトは継続的に作成され、メモリに残りますか?

これらのオブジェクトが使用するメモリは、参照が未使用と見なされた後に最も確実に収集されます。

4. Foo oを宣言しただけの場合;インスタンスを指し示していない場合、メソッドの終了時に破棄されませんか?

いいえ、オブジェクトが作成されていないため、収集するオブジェクトはありません(破棄は正しいWordではありません)。

5.すべてが削除されていることを確認したい場合、それを行う最善の方法は何ですか

オブジェクトのクラスがIDisposableを実装している場合、できるだけ早くDisposeを貪欲に呼び出したいでしょう。 usingキーワードは、Disposeを例外セーフな方法で自動的に呼び出すため、これを簡単にします。

それ以外は、オブジェクトの使用を停止する場合を除いて、他に必要なことは何もありません。参照がローカル変数である場合、スコープから外れると、コレクションの対象になります。1 クラスレベルの変数の場合、nullを変数に割り当てて、包含クラスが適格になる前に適格にする必要があります。


1これは技術的に正しくありません(または少なくとも少し誤解を招く)。オブジェクトは、スコープから外れるずっと前にコレクションに適格になる可能性があります。 CLRは、参照が使用されなくなったことを検出したときにメモリを収集するように最適化されています。極端な場合、CLRはメソッドの1つがまだ実行中であってもオブジェクトを収集できます!

更新:

以下は、GCがまだスコープ内にある場合でもオブジェクトを収集することを示す例です。リリースビルドをコンパイルし、デバッガーの外部でこれを実行する必要があります。

static void Main(string[] args)
{
    Console.WriteLine("Before allocation");
    var bo = new BigObject();
    Console.WriteLine("After allocation");
    bo.SomeMethod();
    Console.ReadLine();
    // The object is technically in-scope here which means it must still be rooted.
}

private class BigObject
{
    private byte[] LotsOfMemory = new byte[Int32.MaxValue / 4];

    public BigObject()
    {
        Console.WriteLine("BigObject()");
    }

    ~BigObject()
    {
        Console.WriteLine("~BigObject()");
    }

    public void SomeMethod()
    {
        Console.WriteLine("Begin SomeMethod");
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("End SomeMethod");
    }
}

私のマシンでは、SomeMethodの実行中にファイナライザーが実行されます!

33
Brian Gideon

.NETガベージコレクターがこれらすべてを処理します。

オブジェクトがいつ参照されなくなったかを判断でき、(最終的に)オブジェクトに割り当てられていたメモリを解放します。

15
Yuck

オブジェクトは、一度ガベージコレクションの対象になります 範囲外に出る 到達不能になります(ありがとう!)。メモリは、ガベージコレクタがメモリ不足を認識していない限り解放されません。

管理対象リソースの場合、ガベージコレクターはこれがいつであるかを認識し、何もする必要はありません。

管理されていないリソース(データベースまたは開かれたファイルへの接続など)の場合、ガベージコレクターは、それらが消費しているメモリの量を知る方法がありません。

オブジェクトが解放されていない場合は、十分なメモリが残っていて必要がないか、アプリケーションでオブジェクトへの参照を維持しているため、ガベージコレクターはそれらを解放しません(実際にこの参照を使用する場合維持)

5
Martin Booth

以下に簡単な概要を示します。

  • 参照がなくなると、オブジェクトは可能性のあるガベージコレクションになります。
  • ガベージへのすべての参照が実際になくなった場合に限り、ヒープサイズを通常に保つ統計収集のみに頼ることができます。つまり、特定のオブジェクトがガベージコレクションされるという保証はありません。
    • また、ファイナライザの呼び出しも保証されません。ファイナライザーを避けます。
  • リークの2つの一般的なソース:
    • イベントハンドラーとデリゲートは参照です。オブジェクトのイベントをサブスクライブする場合、それを参照しています。オブジェクトのメソッドへのデリゲートがある場合、それを参照しています。
    • 定義により、管理されていないリソースは自動的に収集されません。これがIDisposableパターンの目的です。
  • 最後に、オブジェクトの収集を妨げない参照が必要な場合は、WeakReferenceを調べます。

最後に、割り当てなくFoo foo;を宣言する場合、心配する必要はありません-何もリークされません。 Fooが参照タイプの場合、何も作成されませんでした。 Fooが値型である場合、スタックに割り当てられるため、自動的にクリーンアップされます。

2
Kevin Hsu
  1. はい
  2. 同じことはどういう意味ですか?メソッドが実行されるたびに再実行されます。
  3. ありガベージコレクションの詳細については、こちらを参照
  4. はい。メソッドで宣言されたすべての変数のメモリは、メソッドがすべて到達不能であるために終了すると解放されます。さらに、宣言されているが使用されていない変数はコンパイラーによって最適化されるため、実際にはFoo変数がメモリーを占有することはありません。
  5. usingステートメントは、終了時にIDisposableオブジェクトのdisposeを呼び出すだけなので、これは2番目の箇条書きに相当します。両方とも、オブジェクトの処理が完了したことを示し、GCにオブジェクトを手放す準備ができていることを伝えます。オブジェクトへの唯一の参照を上書きすると、同様の効果があります。
2

質問に一つ一つ答えましょう。

  1. はい、このステートメントが実行されるたびに新しいオブジェクトを作成しますが、メソッドを終了すると「スコープ外」になり、ガベージコレクションの対象になります。
  2. これは、文字列型を使用したことを除いて、#1と同じです。文字列型は不変であり、割り当てを行うたびに新しいオブジェクトを取得します。
  3. はい。クラス変数などの大きなスコープを持つ変数にオブジェクトを割り当てない限り、ガベージコレクターはスコープ外のオブジェクトを収集します。
  4. はい。
  5. Usingステートメントは、IDisposableインターフェイスを実装するオブジェクトにのみ適用されます。その場合は、メソッドのスコープ内のオブジェクトに使用するのが最善です。正当な理由がない限り、Foo oを大きなスコープに置かないでください。変数のスコープを意味のある最小のスコープに制限するのが最善です。
2
J Edward Ellis

ガベージコレクターが出現し、参照されなくなったものをクリーンアップします。 Foo内にアンマネージリソースがない限り、Disposeを呼び出すか、その上でusingステートメントを使用しても、あまり役に立ちません。

これはまだC#で​​あったため、これが当てはまると確信しています。しかし、私はXNAを使用してゲームデザインコースを受講し、C#のガベージコレクターについて話をしました。収集するオブジェクトへの参照があるかどうかを確認する必要があるため、ガベージコレクションは高価です。そのため、GCはこれをできる限り延期しようとします。したがって、プログラムが700MBに達したときに物理メモリが不足していない限り、GCが遅延していて、まだ心配していない可能性があります。

ただし、ループの外で_Foo o_を使用し、毎回o = new Foo()を作成するだけであれば、すべて正常に機能するはずです。

1
fire.eagle

ブライアンが指摘するように、GCは、まだスコープ内にあるオブジェクトや、それらのオブジェクトのインスタンスメソッドがまだ実行中であっても、到達不能なものをすべて収集できます。次のコードを検討してください。

class foo
{
    static int liveFooInstances;

    public foo()
    {
        Interlocked.Increment(ref foo.liveFooInstances);
    }

    public void TestMethod()
    {
        Console.WriteLine("entering method");
        while (Interlocked.CompareExchange(ref foo.liveFooInstances, 1, 1) == 1)
        {
            Console.WriteLine("running GC.Collect");
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
        Console.WriteLine("exiting method");
    }

    ~foo()
    {
        Console.WriteLine("in ~foo");
        Interlocked.Decrement(ref foo.liveFooInstances);
    }

}

class Program
{

    static void Main(string[] args)
    {
        foo aFoo = new foo();
        aFoo.TestMethod();
        //Console.WriteLine(aFoo.ToString()); // if this line is uncommented TestMethod will never return
    }
}

デバッグビルド、デバッガ、または指定された行のコメントなしで実行された場合、TestMethodは戻りません。ただし、TestMethodが接続されたデバッガなしで実行すると戻ります。

1
Yaur