ソフトウエアアーキテクトでありコンサルタントでもあるFabrice Marguerie氏がMSDNに記事を書いた。内容は>NETアプリケーションでのリソースとメモリリークの検出方法と防止策についてだ。この記事には、.NETアプリケーションの実装をする過程でどのようにしてメモリやリソースのリークが発生し、どうすれば防げるのかについて説明してある。
C#のような言語は、プログラムがメモリへの参照を完全に失ったとき、ガベージコレクタがメモリ上の後処理を行ってくれるので、本来はメモリリークは発生しない。Fabrice Marguerie氏によるとメモリリークが発生するのは、あるメモリ領域がもう必要なくなったにも関わらず、そのプログラムの一カ所からだけでも参照されている場合だ。この場合、このメモリ領域が到達不可能ならガベージコレクタは再度、解放を行おうとする。しかし、プログラムから参照されているが使われてはいないメモリ領域がある限り、その領域はリークしていることになる。
Fabrice氏は同じようなリークの対象になるOSのリソースを一覧している。
- システムはウィンドウを管理するためにユーザオブジェクトを使っています。ユーザオブジェクトの中に含まれるのは、アクセラレータテーブル、キャレット、カーソル、フック、アイコン、そしてメニューとウィンドウです。
- GDIオブジェクトはグラフィックをサポートします。つまり、ビットマップやブラシ、デバイスコンテキスト(DC)、フォント、メモリDC、メタファイル、パレット、ペンや描画領域等です。
- カーネルオブジェクトがサポートするのはメモリ管理、プロセスの実行、プロセス間通信(IPC)です。つまり、ファイルやプロセス、スレッド、セマフォ、タイマーやアクセストークン、ソケット等を扱います。
これらのリソースには限りがある。リジストリキーGDIProcessHandleQuotaとUSERProcessHandleQuotaにはひとつのプロセスが利用できる最大のGDIオブジェクトとユーザオブジェクトの数が設定されている。デフォルトの値は 10,000だ。ほとんどのアプリケーションではこの値で十分だが、これらのリソースをあまりにも多く使ってしまうとシステムは別種の限界に達してしまう。その限界とは、ひとつのWindowsのセッションのハンドル数が65,536までということだ。しかし、65,536は理論値であり、Fabrice氏の報告よると実際は11,000くらいが上限になる。氏は、システムリソースは注意深く扱う必要があり不必要になったら解放するべきだ、と結論づけている。
氏はどのようにリークが発生するか説明するため、リークの根本原因を一覧している。
- 静的な参照を使っている
- 購読されていないイベント–氏の考えではこれがリークの原因としては最も一般的だ。
- 購読されていない静的なイベント
- Disposeメソッドを呼んでいない
- 不完全なDisposeメソッドを使っている
- WindowsフォームでBindingSourceコンポーネントを使い間違えている
- WorkItem/CABを使うときにRemoveメソッドを呼んでいない
氏の記事にはリークを防ぐための方法についてもアドバイスしている。
- オブジェクトの利用者ではなく、作成者か所有者がオブジェクトの破棄に対して責任を持つこと。
- 不必要なイベントは常に購読しないこと。これはDisposeメソッド内で安全にできます。
- オブジェクトがイベントを生成しなくなったら、イベントのリスナをすべて削除するためにそのオブジェクトにnullを設定すること。
- モデルやビューであるオブジェクトの参照をつかう場合、 ビュー に渡すのはそのオブジェクトのクローンするのがいいでしょう。こうすることで、誰がどのオブジェクトを使っているのか見失わなくなります。
- システムリソースを使うときはusingブロックで囲むことで、ブロックを抜けたときに自動的にDisposeメソッドが呼ばれるようにすること。
Fabrice氏は最後に、オブジェクトやリークを扱うのを支援してくれる次のようなツールを紹介している。GDILeaks (リンク先はEXEファイル)、dotTrace、.NETメモリプロファイラ、SOS.dll、そして WinDbg。