みなさんアプリケーションを開発している際、メモリリークって気にしてますか?
私は、小規模のクラサバ、且つ、非常駐アプリの場合は、正直それほど気にしていません。
しかし、24 時間稼働するアプリケーションだと話は別です。
調査するための基本ロジック
下記は今回のメモリリーク調査で作成したロジックです。
約 1GB のメモリを消費しています。
Private Sub TestMemBase()
Dim objMem1 As New System.IO.MemoryStream(1000000000)
'========== 何かしらの処理 ==========
End Sub
メモリ解放を考慮していないロジック
では、調査するための基本ロジックを踏まえて、下記はメモリ解放の対策をしていないロジックです。
BtnMemTest1 を押下し、TestMem1 メソッドを実行しても問題なく動作します。
BtnMemTest1 を何度押下しても(=TestMem1 メソッドを何度実行しても)問題ないはずです。
Private Sub BtnMemTest1_Click(sender As Object, e As EventArgs) Handles BtnMemTest1.Click
Call Me.TestMem1()
End Sub
Private Sub TestMem1()
Dim objMem1 As New System.IO.MemoryStream(1000000000)
'========== 何かしらの処理 ==========
End Sub
メモリ解放処理をしていなくてもメモリリークが発生しない理由は、TestMem1 メソッドの呼び出しが完了した段階で、.NET 側でメモリ解放処理(ガベージコレクションの発動)を暗黙的におこなっているからです。
では、下記のように BtnMemTest1 を 2 回押下した動作と同じロジックを実行した場合は、どうなるでしょう?
Private Sub BtnMemTest1_Click(sender As Object, e As EventArgs) Handles BtnMemTest1.Click
Call Me.TestMem1()
End Sub
Private Sub TestMem1()
Dim objMem1 As New System.IO.MemoryStream(1000000000)
'========== 何かしらの処理(その1) ==========
Dim objMem2 As New System.IO.MemoryStream(1000000000)
'========== 何かしらの処理(その2) ==========
End Sub
結果は、途中で Out Of Memoty のエラーで落ちますよね。
BtnMemTest1 を 2 回押下した動作と同じ処理を行っているのに、何故、連続で同じロジックを実行するとエラーが発生するのでしょうか?
理由は、objMem1 のメモリ解放処理を実施せずに、同一メソッド内で objMem2 の生成でメモリ消費をしているためです。
BtnMemTest1 を 2 回押下した場合は、TestMem1 メソッドを 2 回呼び出していたので(暗黙的にメモリ解放を行っていたので)、問題なく動作しました。
これは悪い例なので、マネしないようにしてください。
メモリ解放を考慮したロジック
では、メモリリークが発生したロジックに、メモリ解放処理を入れてみましょう!
- 使用済みになった後に、Nothing をセットする。
または、Dispose メソッドが呼び出せる場合は、Dispose メソッド呼び出し後、Nothing をセットする。
ネットで調べると、大体上記の事が出てくると思います。
ソースコードPrivate Sub BtnMemTest1_Click(sender As Object, e As EventArgs) Handles BtnMemTest1.Click
Call Me.TestMem1()
End Sub
Private Sub TestMem1()
Dim objMem1 As New System.IO.MemoryStream(1000000000)
'========== 何かしらの処理(その1) ==========
objMem1.Dispose()
objMem1 = Nothing
Dim objMem2 As New System.IO.MemoryStream(1000000000)
'========== 何かしらの処理(その2) ==========
objMem2.Dispose()
objMem2 = Nothing
End Sub
下記の理由により、objMem2 のメモリ消費時に、objMem1 のメモリを解放しているから問題なく動作します。
・Dispose メソッド : リソースを解放します
・Nothing のセット : ガベージコレクションの処理対象とするためのセット - Using を使用する。
ネットで調べると、Using を使用することにより、リソースが解放されるとの事が出てくると思います。
いざ、実装です!
ソースコードPrivate Sub BtnMemTest2_Click(sender As Object, e As EventArgs) Handles BtnMemTest2.Click
Call Me.TestMem2()
End Sub
Private Sub TestMem2()
Using objMem1 As New System.IO.MemoryStream(1000000000)
'========== 何かしらの処理(その1) ==========
End Using
Using objMem2 As New System.IO.MemoryStream(1000000000)
'========== 何かしらの処理(その2) ==========
End Using
End Sub
結果は…、途中で Out Of Memoty のエラーで落ちちゃいました!!
??です
Using で Dispose されて、ガベージコレクションの処理対象となるのでは??
う~ん…。
色々と調べてみると、Using で作成した MemoryStream は、内部バッファに値が保持されてしまうらしいです。
同一メソッド内で上記のように実装する場合は、Using は使用せずに、New で作成して、明示的に Dispose メソッド呼び出し後 Nothing をセットして使うべきなのかな??
いまいち納得できない部分もありますが、メモリリークを起こさないためには、
- 明示的に解放する(Dispose 等のリソースの解放後、Nothing をセット)
- Using を使用する
を心がけたほうが良いかと思います。
VB.NET メモリリークって気にしてますか?(DataTable 編)もあわせて読んでみてもらえればと思います。