|
.NET では、ガベージコレクションが自動的に行われ、下手なプログラムでも滅多にメモリリークは起こらない。しかし、この危険性は身近に潜んでいる。ここでは、ビットマップ処理を通じでメモリ不足を起こし、システムの挙動を観察する。 ●予備知識 ビットマップなどインスタンスが生成されるクラス(オブジェクト)は、全て参照モードで、 Dim bmp As Bitmap では、bmp が Bitmap を示す入れ物であるとの定義のみで実体はない、しかし、 Dim bmp As New Bitmap(・・・・・・・・・・・・・・・・) などとすれば、bmp には、ビットマップのインスタンス(実体でありメモリ上に展開された意味のあるデータ群)へのポインターが格納される。ポインターとはインスタンスを記述したディバイスコンテキストのハンドルの場所をしめすアドレスの一種。 この状態で問題となるのはインスタンスであり、メモリを食う実体である。通り一遍の処理ならば、特に解放しなくとも、そのアプリが終了した後、暫くの後に自動的に解放される。これは、.NET が、誰からも参照されないオブジェクトを見つけ掃除を行う(ガベージコレクション)からである。 しかし、アプリが実行されている間には、暗黙的な解放は行われない。従って、長大なループ処理内では明示的なオブジェクトの解放が重要となる。正規のオブジェクト寿命内であっても、アプリにとって不要なものは、明示的に解放する必要がある。 →ちなみに、GCクラスを用いれば、ガベージコレクションを強制できる。GC.Collect() ●ビットマップで破綻を起こす ○破綻例1 Dim bmp As Bitmap ループ終了後にDispose しているが、この例では後の祭りである。ループ中の、Bitmap.FromFile で、それぞれインスタンスが生成され、メモリに積上げられている。やがて、システムがピクピクしだし、画面が固まってくるし、ディスクがやたらに動き出す。ひ弱なシステムでは、”メモリ不足です”と言って、死んでしまう。また、最後にあるDispose では、最後に参照したインスタンスのみ解放される。 ○破綻例2 ピクチャーボックスのバックグランドイメージでも起こる。 Dim FP() As String = 沢山のイメージへのパス ピクチャボックスのバックグランドイメージに動的にピクチャを割り当てているが、BackgroundImage は、内部にイメージを保有している訳ではなく、外部を参照しているので、1 と同じことが起こる。 ●回避策 ループ内で、不要になったら解放する。 For i = 0 to 沢山 この例では、bmp は、Bitmap.FromFile(FP(i)) と同じインスタンスを示しているので、bmp.Dispose は、結局、Bitmap.FromFile(FP(i)) で生成されたインスタンスを破棄することと同じとなる。 バックグランドイメージでは、 For i = 0 to 沢山 などとすれば良い。これも上記と同じで、BackgroundImage は、Bitmap.FromFile(FP(i)) と同じインスタンスを示している。ピクチャボックスでは表示するのが目的なので、新しい画像を参照する直前に以前のものを解放するのが良い(表示画像の連続性確保)。 ●実験 以上の二通りについて破綻と回避を実験した。メモリの状態は、システムオブジェクトである Environment.WorkingSet にて、そのスレッドが確保している物理メモリサイズ(バイト数をLong値で得られる)を得、それをグラフにした。実験では、1000 X 700 程度のJPG画像を200回読み込んでいる。横軸は回数、縦軸はメモリサイズ ○結果1 bmpの場合。
赤が破綻、青が回避。赤では、途中で不連続になっている。これは、要求されたメモリを確保できなくなり仮想記憶が生起したことを表す。ディスクがガンガン動き出した。青は毎回解放しているので、20数MB で一定している。
システムメモリが1GBの場合は、この範囲では破綻はしていないが、リソースを独占しているのが分かる。逆に、メモリが十分にあるシステムでは不具合の発見が困難と言える。 ○結果2 ピクチャボックスの場合。
bmp と同じ傾向となる。 |