2018年2月1日木曜日

変数に Nothing をセットしてもリソースは解放できません

VB.net のソースコードを見ていると、使い終わった変数を解放するために Nothing を代入しているコーディングを見かけることがあります。これでは必ずしもリソースは解放されませんし、むしろリークの原因になります。Nothing を代入する意味を理解しておく必要があります。

※ これは2013年に書いたものです。 C# でも Nothing が null になるだけで全く同じです。



例えば次のようなコーディングです。

' ファイルを開く
Dim fsA As FileStream = File.OpenRead( "d:\a.txt" )
…
… ファイルからデータを読み込み(割愛します)
…
' オブジェクトを解放
fsA = Nothing

ファイルストリームやデータベースコネクションなどの変数を利用した後に Nothing をセットしているわけなのですが、コーディングを行った人に理由を聞くと「参考にしたソースコードがそうなっていたから」 というような答えが返ってくることが多いです。おそらくそのソースコードの歴史を遡っていくと VB6 時代の Set fsA = Nothing に行きつくのではないでしょうか。

変数 fsA に Nothing を代入しても、ファイルストリームが使用しているリソースは解放されません。変数 fsA にはファイルストリームの実体への参照先が格納されています。ファイルストリームの実体は別のところにあると考えてください。



変数に格納されているのはただの参照先ですから、
Dim fsB As FileStream = fsA
というように fsA の内容を fsB に代入すると、fsB には同じファイルストリームの実体への参照がコピーされます。


fsA と fsB は同じ実体を参照していますので、どちらを使用しても同じファイルへの操作が行われます。例えば、fsB.Close() を行った後で fsA.Read() を行うと、ファイルストリームが閉じられているため操作できないという例外が発生します。

ここで Nothing の代入に話を戻します。fsA = Nothing によって行われるのは、変数 fsA に格納されている参照先をクリアすることのみです。ファイルストリームの実体はそのまま放置されます。

 .NET Framework ではガベージコレクションというリソース管理の仕組みが導入されており、どこからも参照されなくなったリソースはガベージコレクションによって回収されていきます。しかし、ガベージコレクション自体が負荷のかかる処理であるため、リソースが不足したときなど最小限のタイミングで実行されるようにフレームワークで管理されています。つまり、リソースに余裕があるうちはリソースは回収されずに残ります。ガベージコレクションによって回収されるまでにタイムラグがあるというのが 「変数に Nothing をセットしてもリソースは解放できません」 とした一つめの理由です。

もう一つの理由は、ガベージコレクションの対象はマネージリソースのみであり、アンマネージリソースは必ずしも解放されないということです。アンマネージリソースとは、.NET Framework では管理されないリソースのことです。ファイルやデータベースを操作するとファイルシステムやデータベースシステム側でもリソースが確保されますが、それらは .NET Framework では管理されません。 一般に、アンマネージリソースを利用するクラスでは Dispose メソッドや Close メソッドといったリソース解放のためのメソッドが定義されています。これらを呼び出すようにコーディングする必要があります。呼び出す必要があるから定義されているのです。どのメソッドを呼び出すべきかはそのクラスによって異なりますので、リファレンスを読んで正しくコーディングしてください。解放のためのメソッドを呼び出さないままで変数に Nothing を代入してどこからも参照されていない状態にしてしまうと、アンマネージリソースを解放する手段がなくなってメモリリークが発生する可能性があります。

 有名な話ですが、ODP.NET の OracleConnecion クラスの Close メソッドではコネクションプーリングのためのリソースなどが解放されません。すべてを解放するには Dispose メソッドを呼び出す必要があります。既定ではコネクションプーリングが有効になっていますので、Dispose メソッドを呼び出さずにコネクションの生成を繰り返しているとコネクション数が足りなくなったりします。このように Dispose メソッドと Close メソッドの両方が存在するクラスで両メソッドに違いがある場合があることにも注意してください。

ちなみに VB6 でもこのアンマネージリソースのリークによく似た事象はあります。もっとも有名なのは、Excel 入出力を行うアプリケーションで、アプリケーション終了後も Excel プロセスが残ってしまうというものです。Excel.Application の Quit メソッドを呼び出さなくてはいけません。つまり、VB6 でも Set obj = Nothing では解放できないことがあるのです。多くのオブジェクトでは明示的に解放する必要がないため、そのようなオブジェクトの実装例が十分に説明されないままで慣習化していったんだと思います。

最後に Excel.Application つながりで一つ。Excel.Application では COM オブジェクトが利用されますが、.NET Framework では COM オブジェクトを解放するには System.Runtime.InteropServices.Marshal クラスの ReleaseComObject メソッドを呼び出す必要があります。Quit メソッドだけでは確実に解放されないので注意してください。



0 件のコメント:

コメントを投稿

paiza のスキルチェックをやってみました

いまさら感はありますが、 paiza のスキルチェックをやってみました。指定された時間内にコードを書いてユニットテストにかけ、その結果を基に評価を数値化してくれるというものですが、ゲーム感覚で空き時間を見つけて進めていこうと考えています。 どうやら時間が短いほど高い評価を得...