「メモリリーク」の構造


172

.NETの観点では:

  • メモリリークとはですか?
  • アプリケーションがリークしているかどうかをどのように判断できますか?どのような影響がありますか?
  • どのようにしてメモリリークを防ぐことができますか?
  • アプリケーションにメモリリークがある場合、プロセスが終了するか強制終了されるとアプリケーションは消えますか?または、アプリケーションのメモリリークは、プロセスの完了後でもシステムの他のプロセスに影響を与えますか?
  • また、COM InteropやP / Invokeを介してアクセスされるアンマネージコードについてはどうでしょうか。

回答:


110

私が見た中で最も良い説明は、無料のプログラミングの基礎e-bookの第7章です。

基本的に、.NET、参照されたオブジェクトがルート化されている場合にメモリリークが発生するため、ガベージコレクションを実行できません。これは、意図した範囲を超えて参照を保持していると、誤って発生します。

OutOfMemoryExceptionsの取得を開始するとリークが発生するか、メモリ使用量が予想を超える(PerfMonは優れたメモリカウンターがあります)。

.NETのメモリモデルを理解することは、それを回避する最良の方法です。具体的には、ガベージコレクターのしくみと参照のしくみについて理解しています。ここでも、電子書籍の第7章を参照します。また、一般的な落とし穴、おそらく最も一般的なイベントであることにも注意してください。オブジェクトAがオブジェクトBのイベントに登録されている場合、BA参照を保持しているため、オブジェクトAはオブジェクトBが消えるまで留まります。解決策は、完了したらイベントの登録を解除することです。

もちろん、適切なメモリプロファイルを使用すると、オブジェクトグラフを表示し、オブジェクトのネスト/参照を調べて、参照がどこから来ているのか、どのルートオブジェクトが原因であるのかを確認できます(red-gate ant プロファイル、JetBrains dotMemory、memprofilerは非常に優れています)選択肢、またはテキストのみのWinDbgSOSを使用できますが、実際の第一人者でない限り、商用/ビジュアル製品を強くお勧めします)。

アンマネージコードは、共有参照がガベージコレクターによって管理されることを除いて、通常のメモリリークの影響を受けると思います。この最後の点について私は間違っているかもしれません。


11
ああ、あなたは本が好きですか?私は作者がスタックオーバーフローで時々ポップアップするのを見ました。
Johnno Nolan、

一部の.NETオブジェクトは、自分自身をルート化して収集できなくなる場合もあります。このため、IDisposableのものはすべて破棄する必要があります。
共龍

1
@kyoryu:オブジェクトはどのようにそれ自身をルート化しますか?
AndreiRînea2010

2
@Andrei:実行中のスレッドは、おそらくオブジェクト自身をルート化するオブジェクトの最良の例だと思います。静的な非パブリックの場所に自身への参照を配置するオブジェクト(静的イベントへのサブスクライブ、または静的フィールドの初期化によるシングルトンの実装など)も、明確な方法がないため、ルート化されている可能性があります... um ...係留からそれを「アップルート」します。
Jeffrey Hantin、2011年

@Jeffryこれは何が起こっているのかを説明する非定型的な方法であり、私はそれが好きです!
Exitos

35

厳密に言えば、メモリリークは、プログラムで「使用されなくなった」メモリを消費しています。

「もう使用されていません」には複数の意味があります。「参照できなくなった」、つまり完全に回復できない、または参照されている、回復可能な、使用されていないがプログラムは参照を保持しているという意味です。完全に管理されたオブジェクトの場合、後者のみが.Netに適用されます。ただし、すべてのクラスが完璧なわけではなく、ある時点で、基になるアンマネージ実装がそのプロセスのリソースを永続的にリークする可能性があります。

すべての場合において、アプリケーションは、必要以上に多くのメモリを消費します。リークされた量に応じて、副作用はゼロから、過剰な収集によるスローダウン、一連のメモリ例外、最後に致命的なエラー、そして強制的なプロセスの終了に至る可能性があります。

ガベージコレクションサイクルのたびに、より多くのメモリがプロセス割り当てられることが監視で示される場合、アプリケーションにメモリの問題があることがわかります。。このような場合、メモリに多くを保持している、またはいくつかの基になるアンマネージ実装がリークしています。

ほとんどのリークでは、プロセスが終了するとリソースが回復しますが、一部のリソースは必ずしも正確に回復されない場合があります。GDIカーソルハンドルはそのことで有名です。もちろん、プロセス間通信メカニズムがある場合、他のプロセスで割り当てられたメモリは、そのプロセスが解放するか終了するまで解放されません。


32

「メモリリークとは」および「影響とは」の質問はすでに十分に回答されていると思いますが、他の質問にいくつか追加したいと思います...

アプリケーションがリークしているかどうかを理解する方法

一つの興味深い方法は、オープンにあるパフォーマンスモニタとのトレースを追加し、すべてのヒープで#バイト#のGen 2つのコレクションちょうどあなたのプロセスを見て、それぞれの場合において、。特定の機能を実行すると合計バイト数が増加し、そのメモリが次のGen 2の収集後も割り当てられたままである場合、その機能によりメモリリークが発生していると言えます。

防ぐ方法

他の良い意見が与えられました。おそらく、.NETメモリリークの最も一般的に見落とされている原因は、イベントハンドラーを削除せずにオブジェクトに追加することです。オブジェクトにアタッチされたイベントハンドラーは、そのオブジェクトへの参照の形式であるため、他のすべての参照がなくなった後でも、収集を防止します。常にイベントハンドラーを切り離すことを忘れないでください(-=C#の構文を使用)。

プロセスが終了するとリークはなくなりますか?COM相互運用はどうですか?

プロセスが終了すると、そのアドレススペースにマップされたすべてのメモリが、DLLから提供されるCOMオブジェクトを含め、OSによって再利用されます。比較的まれに、COMオブジェクトが別のプロセスから提供される場合があります。この場合、プロセスが終了しても、使用したすべてのCOMサーバープロセスに割り当てられたメモリを管理する必要があります。


19

私は、メモリリークを、オブジェクトが完了後に割り当てられたすべてのメモリを解放しないオブジェクトとして定義します。フレームワークとサードパーティコンポーネントでWindows APIとCOM(つまり、バグがあるか、正しく管理されていないアンマネージコード)を使用している場合、これがアプリケーションで発生する可能性があることがわかりました。また、ペンなどの特定のオブジェクトを使用した後に片付けをしないと問題が発生する可能性があることもわかりました。

個人的には、発生する可能性があるが、ドットネットアプリケーションでのメモリリークに限定されないメモリ不足例外が発生しています。(OOMはピン留めから取得することもできます。PinningArticalを参照してください。)。OOMエラーが発生しない場合、またはメモリリークが原因であるかどうかを確認する必要がある場合は、アプリケーションをプロファイルするしか方法がありません。

私はまた、次のことを確認します。

a)Idisposableを実装するすべてのものは、finallyブロックまたはusingステートメントのいずれかを使用して破棄されます。これらには、ブラシ、ペンなどが含まれます(一部の人々は、すべてをさらに何にも設定しないと主張します)。

b)closeメソッドを持つものは、finallyまたはusingステートメントを使用して再び閉じられます(usingステートメントの外でオブジェクトを宣言したかどうかに応じて、usingが常に閉じるとは限りません)

c)アンマネージコード/ Windows APIを使用している場合、これらは後で正しく処理されます。(一部はリソースを解放するためのクリーンアップメソッドを持っています)

お役に立てれば。


19

.NETのメモリリークを診断する必要がある場合は、次のリンクを確認してください。

http://msdn.microsoft.com/en-us/magazine/cc163833.aspx

http://msdn.microsoft.com/en-us/magazine/cc164138.aspx

これらの記事では、プロセスのメモリダンプを作成する方法とそれを分析して、リークが管理されていないか管理されているかを最初に判断できるようにする方法と、管理されている場合に、それがどこから発生しているかを把握する方法について説明しています。

Microsoftには、DebugDiagと呼ばれる、ADPlusに代わるクラッシュダンプの生成を支援する新しいツールもあります。

http://www.microsoft.com/downloads/details.aspx?FamilyID=28bd5941-c458-46f1-b24d-f60151d875a3&displaylang=en


16

MicrosoftのCLR Profilerを使用するhttp://www.microsoft.com/downloads/details.aspx?familyid=86ce6052-d7f4-4aeb-9b7a-94635beebdda&displaylang=enは、どのオブジェクトがメモリを保持しているか、どの実行フローがリードしているかを判断する優れた方法ですこれらのオブジェクトの作成と、ヒープ上のどこにどのオブジェクトが存在するかを監視する(フラグメンテーション、LOHなど)。


15

ガベージコレクターがどのように機能するかを最もよく説明しているのは、C#ブックを介したJeff Richters CLR(Ch。20)です。これを読むと、オブジェクトがどのように存続するかを理解するための優れた基礎が得られます。

オブジェクトを誤ってルート化する最も一般的な原因の1つは、クラス外のイベントをフックすることです。外部イベントを接続した場合

例えば

SomeExternalClass.Changed += new EventHandler(HandleIt);

また、破棄するときにフックを解除することを忘れると、SomeExternalClassがクラスへの参照を持ちます。

上記のように、SciTechメモリプロファイラーは、リークしていると思われるオブジェクトのルートを示すのに優れています。

しかし、特定のタイプをチェックする非常に迅速な方法として、WnDBGを使用するだけです(これは、接続されているときにVS.NETイミディエイトウィンドウでも使用できます)。

.loadby sos mscorwks
!dumpheap -stat -type <TypeName>

次に、そのタイプのオブジェクトを破棄すると思われる処理を実行します(たとえば、ウィンドウを閉じる)。実行するどこかにデバッグボタンを置くと便利です。System.GC.Collect()、数回れる。

その後、!dumpheap -stat -type <TypeName>再度実行します。数が減少しなかった場合、または期待したほど減少しなかった場合は、さらに調査するための根拠があります。(このヒントは、Ingo Rammerによるセミナーから得ました)。


14

管理された環境では、リークは、メモリの大きなチャンクへの不要な参照を保持し続けることになると思います。


11

.NETのメモリリークが他のリークと同じではないと人々はなぜ思うのでしょうか。

メモリリークとは、リソースに接続し、それを手放さない場合です。これは、マネージコーディングとアンマネージコーディングの両方で行うことができます。

.NETやその他のプログラミングツールに関しては、ガベージコレクションや、アプリケーションのリークを引き起こす状況を最小限に抑えるその他の方法についてのアイデアがあります。ただし、メモリリークを防止する最良の方法は、使用しているプラ​​ットフォームで、基盤となるメモリモデルと、その仕組みを理解する必要があることです。

GCやその他の魔法によって混乱が解消されると信じることは、メモリリークへの近道であり、後で見つけるのは困難です。

管理されていないコードを作成するときは、通常、必ずクリーンアップを行います。つまり、管理者のリソースではなく、管理するリソースがクリーンアップの責任になります。

一方、.NETでは、GCがすべてをクリーンアップすると多くの人が考えています。まあ、それはあなたのためにいくつかを行いますが、あなたはそれがそうであることを確認する必要があります。.NETは多くのことをラップするため、マネージリソースとアンマネージリソースのどちらを扱っているかは常にわかりません。また、何を扱っているかを確認する必要があります。フォント、GDIリソース、Active Directory、データベースなどの処理は、通常、注意する必要があります。

管理された用語では、プロセスが強制終了または削除されると、それが消えると言うために、首に線を引きます。

多くの人がこれを持っていると思いますが、これが終わればいいと思います。アプリを終了して混乱を解消するようユーザーに要求することはできません!IEやFFなどのブラウザを見て、Googleリーダーなどを開いて、数日間そのままにして、何が起こるかを見てみましょう。

次に、ブラウザーで別のタブを開き、いくつかのサイトに移動し、ブラウザーのリークを引き起こした他のページをホストしていたタブを閉じた場合、ブラウザーがメモリを解放すると思いますか?IEではそうではありません。私のコンピューターでは、Googleリーダーを使用すると、IEは短時間(約3〜4日)で1 GiBのメモリを簡単に消費します。さらに悪いニュースページもあります。


10

管理された環境では、リークは、メモリの大きなチャンクへの不要な参照を保持し続けることになると思います。

もちろんです。また、使い捨てオブジェクトで.Dispose()メソッドを適切に使用しないと、メモリリークが発生する可能性があります。これを行う最も簡単な方法は、最後に.Dispose()を自動的に実行するusingブロックを使用することです。

StreamReader sr;
using(sr = new StreamReader("somefile.txt"))
{
    //do some stuff
}

また、アンマネージオブジェクトを使用するクラスを作成する場合、IDisposableを正しく実装していないと、クラスのユーザーにメモリリークが発生する可能性があります。


9

すべてのメモリリークは、プログラムの終了によって解決されます。

十分なメモリをリークすると、オペレーティングシステムがユーザーに代わって問題を解決する場合があります。


8

.netでmemリークがどうなるかについて、バーナードに同意します。

アプリケーションのプロファイルを作成してメモリの使用状況を確認し、大量のメモリを管理する必要がない場合にリークがあると判断できます。

管理された用語では、プロセスが強制終了または削除されると、それが消えると言うために、首に線を引きます。

アンマネージコードはそれ自体が野獣であり、コード内にリークが存在する場合、標準のメモリに従います。リークの定義。


7

また、.NETには2つのヒープがあり、1つはラージオブジェクトヒープであることに注意してください。およそ85k以上のオブジェクトがこのヒープに置かれていると思います。このヒープには、通常のヒープとは異なるライフタイムルールがあります。

大きなメモリ構造(辞書またはリスト)を作成している場合は、正確なルールを調べてみるのが賢明です。

プロセスの終了時にメモリを再利用する限り、実行中のWin98またはそれに相当するものを除いて、すべてが終了時にOSに解放されます。唯一の例外は、プロセス間で開かれているものであり、別のプロセスがまだリソースを開いています。

COMオブジェクトは扱いにくい場合があります。いつもIDisposeパターンを使えば安心です。しかし、を実装するいくつかの相互運用機能アセンブリに遭遇しましたIDispose。ここでの鍵は、Marshal.ReleaseCOMObject使い終わったら電話することです。COMオブジェクトは引き続き標準のCOM参照カウントを使用します。



弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.