2014年7月14日月曜日

C#でのメモリリーク

Microsoftに詳細は記載されている。
http://msdn.microsoft.com/ja-jp/magazine/cc163491.aspx
しかし、よくわからないので、分かりやすくまとめてみました。


C#のメモリ管理
プログラムがメモリへの参照を完全に失ったとき、ガベージコレクタがメモリ上の後処理を行ってくれるので、本来はメモリリークは発生しない。メモリリークが発生するのは、あるメモリ領域がもう必要なくなったにも関わらず、そのプログラムの一カ所からだけでも参照されている場合だ。この場合、このメモリ領域が到達不可能ならガベージコレクタは再度、解放を行おうとする。しかし、プログラムから参照されているが使われてはいないメモリ領域がある限り、その領域はリークしていることになる。

メモリリークの発生をチェック
タスク マネージャで、メモリの使用量が徐々に (または急激に) 増加していることを確認できます。メモリ リークが発生している可能性があるときは、まず、どの種類のメモリでリークが発生しているかを判断する必要があります。これにより、適切な領域でデバッグ 作業を行うことができます。PerfMon{パフォーマンスモニタ) を使用して、リソースリークが発生しているかどうか判別します。
デフォルトの表示列では、どのプロセスがどれくらいメモリを使用しているのか分かりますが、リソースリークを調べたい場合は次の手順で表示列を増やします。

1.「プロセス」タブをクリックする。
2.メニューバーの「表示」をクリックし、「列の選択」をクリックする。
3. 「ハンドル」、「USERオブジェクト」、「GDIオブジェクト」にチェックを付けてOKをクリックする


 これで、ハンドル数、USERオブジェクト数、GDIオブイェクト数がプロセス毎に現在どれだけ使用されているか一目で分かります。


リーク原因
リソースには限りがある。ひとつのプロセスが利用できる最大のGDIオブジェクトとユーザオブジェクトの数が設定されている。デフォルトの値は 10,000だ。システムリソースは注意深く扱う必要があり不必要になったら解放するべきだ。

以下原因が考えられる。
・静的な参照を使っている
 staticなフィールドはルート参照であり,そこから(強)参照で辿れるインスタンスは,解放されることがありません。
 クラスTのstaticなフィールドにList<T>を用意し,TのコンストラクタでそのフィールドにthisをAddしているような場合に、staticなフィールドから辿れるインスタンスは,明示的にListからRemoveするなり,nullを代入するなりして,参照を辿れなくする必要があります。

購読されていないイベント ⇒これがリークの原因としては最も一般的
 C#なら+=でイベント源に登録しますが、 イベントリスナーのオブジェクトが不要になったとき、イベント源に登録の解除(JavaならremoveListener、C#なら-= )をしなければいけません。
・購読されていない静的なイベント
Disposeメソッドを呼んでいない
・不完全なDisposeメソッドを使っている
・WindowsフォームでBindingSourceコンポーネントを使い間違えている
・WorkItem/CABを使うときにRemoveメソッドを呼んでいない

対応策としては以下を考慮する。
・オブジェクトの利用者ではなく、作成者か所有者がオブジェクトの破棄に対して責任を持つこと。
 ⇒Dispose内で、不要になったイベントなどのリソースの開放を行う 
不必要なイベントは常に購読しないこと。
・オブジェクトがイベントを生成しなくなったら、イベントのリスナをすべて削除するためにそのオブジェクトにnullを設定すること。
・モデルやビューであるオブジェクトの参照をつかう場合、 ビュー に渡すのはそのオブジェクトのクローンするのがいいでしょう。こうすることで、誰がどのオブジェクトを使っているのか見失わなくなります。
システムリソースを使うときはusingブロックで囲むことで、ブロックを抜けたときに自動的にDisposeメソッドが呼ばれるようにすること。
・PropertyChangedは使わない
 ⇒イベントは便利な反面イベントの中でイベントを発火する連鎖を行ったり、解放し忘れたり、イベントの提供元が影響度を追えなくなったりするので、どうしても使うときには個別プロパティのChangedイベントを作成する。


まとめと「netはメモリを自動開放してくれるがリソース開放は明示的に記述する必要があります。後からハマる前にコーディング規約にしっかりとリソース開放してくれるusing句の記述を徹底し、安全なアプリケーションが出来上がるようにします」。

 ※ C#で、あるクラスのオブジェクトがリークしてるかどうかは、デストラクタ(ファイナライザ)を実装してメッセージを出力するようにして、GC.Collect()で強制的にガベージコレクションをさせてみると確認できます。
 GC.GetTotalMemory(false)を使えば、その時点で使用されているメモリ量が分かりますので、GC.Collectでの変化量を確認することができます。

GCの行われる時
メモリが不足してきた場合や、明示的に動作を指示された場合にのみ行われる。つまり、メモリが潤沢に余っている場合には、プロセスの使用するメモリ量が増加するのが正常な動作である。そのままメモリ不足でプログラムが停止するのではないかと懸念する必要はない。メモリが不足してくれば、自動的に回収が行われる。
 そのため、プロセスが使用するメモリ量が増加しているとしても、通常はそれに対してプログラマー自身が何かの対処をする必要はない。むしろ、メモリ管理に下手に手を出そうとすると、かえって効率を落とす可能性がある。

Windbg
メモリーリークをインスタンス単位で追跡するには Windbg を使用します。
WindbgはWindowsSDKに含まれています。






 藤沢市藤沢本町の整体 長生療術
   みつお治療院

~長生療術・整体・マッサージ・骨盤矯正~
腰痛・肩こり・自律神経失調症・産後骨盤矯正




 Cafe de Feliz (カフェ・ド・フェリース)

茅ヶ崎駅北口から徒歩1分ヨーロピアンな空間で過ごすのんびりティータイム 



                  いしん居宅介護支援事業所



0 件のコメント:

コメントを投稿