16

「The C# Language」、第 4 版を読んでいます。ガベージ コレクションについて次のように説明しています。

「BILL WAGNER: 次のルールは、C# と他のマネージド環境との重要な違いです。

そのようなクリーンアップが抑制されていない限り (たとえば、ライブラリ メソッド GC.SuppressFinalize の呼び出しによって)、アプリケーションの終了前に、まだガベージ コレクションされていないすべてのオブジェクトのデストラクタが呼び出されます。"

ここでいくつか質問があります。

  • Q1. .net が他のマネージド環境と異なるのはなぜですか (これは Java を示唆していると思いますか?)。特定の設計上の懸念はありますか?

  • Q2. GC.SuppressFinalize呼び出されたオブジェクトはどうなりますか? これは、GC がそのようなオブジェクトのファイナライザー (デストラクタ) を呼び出さないことを意味することを理解しています。そうしないと、メモリ リークが発生しますか?

4

5 に答える 5

40

GC.SuppressFinalize が呼び出されたオブジェクトはどうなりますか? これは、GC がそのようなオブジェクトのファイナライザー (デストラクタ) を呼び出さないことを意味することを理解しています。そうしないと、メモリリークが発生しますよね?

ファイナライズの目的を誤解しています。ファイナライズは、マネージド メモリではないリソースをクリーンアップするためのものです。

整数フィールドを含む参照型のオブジェクトがあるとします。その整数フィールドは、アンマネージ コードを呼び出してファイルを開くことによって取得されたファイルへのハンドルです。

他のプログラムがそのファイルにアクセスする可能性があるため、できるだけ早くファイルを閉じることをお勧めします。しかし、.NET ランタイムは、この整数がオペレーティング システムにとって特別な意味を持つことを認識していません。それはただの整数です。

通常、この問題を解決する方法は、オブジェクトを IDisposable を実装するものとしてマークし、処理が完了したらすぐにオブジェクトで "Dispose" を呼び出すことです。「Dispose」の実装により、ファイルが閉じられます。

ここでは特別なことは何もしていないことに注意してください。アンマネージ リソースをクリーンアップするメソッドを "Dispose" と呼び、破棄する必要があるオブジェクトが IDisposable を実装するのは単なる慣習です。ガベージ コレクションは、これについてまったく何も知りません。

ここで問題が発生します。誰かが Dispose を呼び出すのを忘れたらどうなるでしょうか。ファイルは永久に開いたままになりますか? (明らかに、プロセスが終了するとファイルは閉じられますが、プロセスが長時間実行されるとどうなりますか?)

この問題を解決するには、ファイナライザーを使用します。それはどのように機能しますか?

オブジェクトがガベージ コレクションの対象になると、ガベージ コレクターはオブジェクトをチェックして、ファイナライザーがあるかどうかを確認します。存在する場合は、ガベージ コレクションの代わりにファイナライザー キューに入れます。将来の特定されていない時点で、スレッドが実行され、キューが調べられ、すべてのオブジェクトに対して特別な「Finalize」メソッドが呼び出されます。その後、オブジェクトはファイナライズ キューから削除され、「ファイナライズ済みです」とマークされます。オブジェクトは再びコレクションの対象となるため、最終的にガベージ コレクターが実行され、ファイナライズ キューに置かれることなくオブジェクトが収集されます。

明らかに、"Finalize" と "Dispose" はしばしば同じことをする必要があります。

しかし今、別の問題が発生しています。オブジェクトを破棄するとします。今はファイナライズする必要はありません。ファイナライズにはコストがかかります。死んだオブジェクトを必要以上に長く生き続けます。したがって、伝統的にオブジェクトを破棄するとき、Dispose の実装はアンマネージ リソースを閉じるだけでなく、オブジェクトを「このオブジェクトは既にファイナライズされているため、再度ファイナライズしないでください」とマークします。こうすることで、ガベージ コレクターを騙して、オブジェクトをファイナライズ キューに置かないようにします。

それでは、具体的な質問に答えましょう。

GC.SuppressFinalize が呼び出されたオブジェクトはどうなりますか?

オブジェクトが死んでいる場合、ガベージ コレクターはオブジェクトをファイナライザー キューに入れることなく、単にオブジェクトのメモリを再利用します。

これは、GC がそのようなオブジェクトのファイナライザーを呼び出さないことを意味することを理解しています

GCがファイナライザーを呼び出すことはありません。ファイナライザー スレッドは、ファイナライザーを呼び出す唯一のものです。

これらのオブジェクトが実際に破壊されるのはいつですか?

「破壊された」の意味が明確ではありません。「ファイナライザーはいつ実行されますか?」という意味であれば、ファイナライズを抑制すると言ったので、答えは「決して」です。「マネージド ヒープ内のメモリが再利用されるのはいつですか?」という意味であれば、答えは「ガベージ コレクターによってオブジェクトが無効であると識別され次第」です。オブジェクトはファイナライザー キューによって保持されないため、通常よりも早く発生します。

于 2011-07-11T15:11:59.630 に答える
2

私が知っているのは 2 番目の答えだけです:SuppressFinalizeは、オブジェクトが既に破棄されている場合、つまり によって呼び出されIDisposable.Disposeます。したがって、再び破壊されるべきではありません。

これは、ファイナライザーが非決定論的なタイミングで発生するリソースのクリーンアップであるためです。IDisposable特定の予測可能な時間に発生するリソースのクリーンアップを許可するために追加されました。

編集:プロセスの終了時に、メモリ リークは重要ではありません。プロセスが終了すると、そのヒープは Windows によって収集されるため、オブジェクトのメモリがヒープに返されるかどうかは問題ではありません。

于 2011-07-11T14:55:40.717 に答える
1

私はQ2に答えようとします:

クラスにファイナライザー (~ClassName で示される) がある場合、ガベージ コレクションによって直接収集されるのではなく、後で呼び出されるファイナライザー キューに置かれます。ファイナライザーが最終的に呼び出されると、通常、作成されたクラスである管理されていないリソースが解放されます。なんらかの理由で、通常は dispose を呼び出してユーザーが既にそれを行っている場合、ファイナライザーはアンマネージ メモリを消去する必要がないため、SuppressFinalizer を呼び出す必要があります。そうしないと、リソースを再度解放しようとします。したがって、唯一のメモリ リークは、クラスの作成時に要求したリソースによるものです。ファイナライザーは、フレームワークによってまだ管理されていないすべてのものを解放するだけです。

于 2011-07-11T14:58:43.727 に答える