.NET でのメモリ リーク [終了]
-
09-06-2019 - |
質問
.NET でメモリ リークが発生する可能性のあるすべての原因は何ですか?
私が知っているのは次の 2 つです。
- 正しく登録が解除されていない イベントハンドラー/デリゲート.
- Windows フォームで動的子コントロールを破棄しない場合:
例:
// Causes Leaks
Label label = new Label();
this.Controls.Add(label);
this.Controls.Remove(label);
// Correct Code
Label label = new Label();
this.Controls.Add(label);
this.Controls.Remove(label);
label.Dispose();
アップデート:目的は、あまり明らかではない一般的な落とし穴 (上記など) をリストすることです。通常、ガベージ コレクターのせいでメモリ リークは大きな問題にはならないという考えが一般的です。以前の C++ とは異なります。
素晴らしい議論ですが、はっきりさせてください...定義上、.NET 内のオブジェクトへの参照が残っていない場合、そのオブジェクトはいつかガベージ コレクトされます。したがって、これはメモリリークを引き起こす方法ではありません。
管理された環境では、認識していないオブジェクトへの意図しない参照があった場合、それはメモリ リークであると考えられます (したがって、私の質問には 2 つの例があります)。
では、このようなメモリ リークが発生する可能性のあるさまざまな方法にはどのようなものがあるでしょうか?
解決
ファイナライザー スレッドをブロックします。ファイナライザー スレッドのブロックが解除されるまで、他のオブジェクトはガベージ コレクションされません。したがって、使用されるメモリの量はますます増加します。
参考文献: http://dotnetdebug.net/2005/06/22/blocked-finalizer-thread/
他のヒント
これは実際にリークを引き起こすわけではなく、GC の作業が増えるだけです。
// slows GC
Label label = new Label();
this.Controls.Add(label);
this.Controls.Remove(label);
// better
Label label = new Label();
this.Controls.Add(label);
this.Controls.Remove(label);
label.Dispose();
// best
using( Label label = new Label() )
{
this.Controls.Add(label);
this.Controls.Remove(label);
}
.Net のような管理された環境では、使い捨てコンポーネントをこのように放置したままにしても、それほど問題になることはありません。これが管理の意味の大きな部分を占めています。
確かに、アプリの速度が遅くなります。しかし、他のもののために混乱を残すことはありません。
の設定 グリッドコントロール.データソース BindingSource クラスのインスタンスを使用せずに、プロパティを直接使用します (http://msdn.microsoft.com/en-us/library/system.windows.forms.bindingsource.aspx).
これによりアプリケーションでリークが発生し、プロファイラーで追跡するのにかなりの時間がかかりました。最終的に、Microsoft が回答した次のバグ レポートを見つけました。 http://connect.microsoft.com/VisualStudio/フィードバック/ViewFeedback.aspx?FeedbackID=92260
BindingSource クラスのドキュメントで Microsoft がこのクラスをよく考えられた正当なクラスであるかのように見せかけているのは面白いことですが、通貨マネージャーとグリッド コントロールへのデータのバインドに関する根本的なリークを解決するためにこのクラスを作成しただけだと思います。
これには気をつけてください。このせいで、漏洩するアプリケーションが大量に存在していると思います。
包括的なリストを提供する方法はありません...これは、「どうすれば濡れますか?」と尋ねることに非常に似ています。
ただし、IDisposable を実装するすべてのものに対して Dispose() を呼び出していることを確認し、あらゆる種類のアンマネージ リソースを消費するすべての型に対して IDisposable を実装していることを確認してください。
時々、コードベースで FxCop などを実行して、そのルールを適用してください。一部の使い捨てオブジェクトがアプリケーション フレームワーク内にどれほど深く埋め込まれているかに驚かれるでしょう。
Finalize (またはファイナライザーからの Dispose 呼び出し) メソッドの例外により、アンマネージ リソースが正しく破棄されません。よくあるのはプログラマーによるものです 仮定して オブジェクトがどの順序で破棄されるか、すでに破棄されているピア オブジェクトを解放しようとすると例外が発生し、残りの Finalize/Dispose from Finalize メソッドは呼び出されません。
この議論にさらに 4 つの項目を追加します。
このようなイベントを適切に準備せずに UI コントロールを作成したスレッド (Thread.Abort()) を終了すると、メモリが予期して使用される可能性があります。
Pinvoke を通じてアンマネージド リソースにアクセスし、それらをクリーンアップしないと、メモリ リークが発生する可能性があります。
大きな文字列オブジェクトを変更する。必ずしもメモリ リークであるわけではありません。範囲外になれば GC が処理します。ただし、プログラムのフットプリントを保証するために GC に実際に依存することはできないため、大きな文字列が頻繁に変更されると、パフォーマンスの点でシステムがヒットする可能性があります。最小限。
カスタム描画を実行するために GDI オブジェクトを頻繁に作成します。GDI 作業を頻繁に実行する場合は、単一の gdi オブジェクトを再利用します。
毎回 IDisposable を呼び出すのが最も簡単な開始点であり、コードベース内の簡単に解決できるメモリ リークの成果をすべて取得する効果的な方法であることは間違いありません。しかし、それだけでは必ずしも十分ではありません。たとえば、マネージド コードが実行時にいつどのように生成されるかを理解し、アセンブリがアプリケーション ドメインに読み込まれるとアンロードされないため、アプリケーションのフットプリントが増加する可能性があることを理解することも重要です。
.NET メモリ リークを防ぐには:
1) 'IDisposable' インターフェイスを持つオブジェクトが作成されるときは常に、'using' コンストラクト (または 'try-finally コンストラクト) を使用します。
2) クラスがスレッドを作成する場合、または静的または長期存続するコレクションにオブジェクトを追加する場合は、クラスを「IDisposable」にします。C# の「イベント」はコレクションであることに注意してください。
ここに短い記事があります メモリリークを防ぐためのヒント.
予期せぬメモリ使用量や実際のリークについて話しているのでしょうか?あなたがリストした 2 つのケースは、正確にはリークではありません。それらは、物体が意図したよりも長く残留するケースです。
言い換えれば、それらはメモリリークと呼ぶ人が知らなかった、または忘れていた参照です。
編集:または、ガベージ コレクターまたは非マネージ コードの実際のバグです。
編集2:これについて考えるもう 1 つの方法は、オブジェクトへの外部参照が常に適切に解放されるようにすることです。外部とは、制御範囲外のコードを意味します。このような状況が発生した場合は、メモリが「リーク」する可能性があります。
- 不要になったオブジェクトへの参照を保持します。
他のコメントについて - Dispose が確実に呼び出されるようにする 1 つの方法は、using... を使用することです。コード構造が許可する場合。
私にとって本当に予想外だったことの 1 つは次のとおりです。
Region oldClip = graphics.Clip;
using (Region newClip = new Region(...))
{
graphics.Clip = newClip;
// draw something
graphics.Clip = oldClip;
}
メモリリークはどこにあるのでしょうか?そう、処分すべきだった oldClip
, 、 あまりにも!なぜなら Graphics.Clip
これは、ゲッターが呼び出されるたびに新しい使い捨てオブジェクトを返す珍しいプロパティの 1 つです。
アンマネージ言語でメモリ リークを引き起こす可能性のあるものの多くは、マネージ言語でもメモリ リークを引き起こす可能性があります。例えば、 不適切なキャッシュポリシー メモリリークが発生する可能性があります。
しかし、グレッグとダニーが言ったように、包括的なリストはありません。耐用期間を過ぎたメモリを保持する可能性のあるものはすべて、リークを引き起こす可能性があります。
デッドロックされたスレッドはルートを解放しません。明らかに、デッドロックがより大きな問題を引き起こしていると主張することもできます。
ファイナライザー スレッドがデッドロックすると、残りのすべてのファイナライザーが実行できなくなり、すべてのファイナライズ可能なオブジェクトが再利用されなくなります (オブジェクトはまだ到達可能リストによってルート化されているため)。
マルチ CPU マシンでは、ファイナライザー スレッドがファイナライザーを実行するよりも速く、ファイナライズ可能なオブジェクトを作成できます。それが続く限り、メモリが「リーク」されてしまいます。これが実際に起こる可能性はほとんどありませんが、簡単に再現できます。
ラージ オブジェクト ヒープは圧縮されていないため、断片化によってメモリ リークが発生する可能性があります。
手動で解放する必要があるオブジェクトが多数あります。例えば。リースとアセンブリのないリモート オブジェクト (AppDomain をアンロードする必要がある)。