以前、メモリリーク対策について MemoryStream、DataTable の解放方法を記載しました。
今回は、頻繁に使うであろうジェネリックコレクションの List クラスについてを記載します!
調査するための基本ロジック
下記は今回のメモリリーク調査で作成したロジックです。
約 1GB のメモリを消費しています。
List クラスということで、Add メソッドで追加してみました。
Private Sub TestListBase()
Dim objList1 As New List(Of System.IO.MemoryStream)
'========== 約 1GB のメモリを消費 ==========
For i = 1 To 10
'約 100MB のオブジェクトを生成
Dim objMem As New System.IO.MemoryStream(100000000)
'========== 何かしらの処理 ==========
objList1.Add(objMem)
Next
End Sub
メモリ解放を考慮していないロジック
では、調査するための基本ロジックを踏まえて、下記はメモリ解放の対策をしていないロジックです。
BtnListTest1 を押下し、TestList1 メソッドを実行しても問題なく動作します。
BtnListTest1 を何度押下しても(=TestList1 メソッドを何度実行しても)問題ないはずです。
Private Sub BtnListTest1_Click(sender As Object, e As EventArgs) Handles BtnListTest1.Click
Call Me.TestList1()
End Sub
Private Sub TestList1()
Dim objList1 As New List(Of System.IO.MemoryStream)
'========== 約 1GB のメモリを消費 ==========
For i = 1 To 10
'約 100MB のオブジェクトを生成
Dim objMem As New System.IO.MemoryStream(100000000)
'========== 何かしらの処理 ==========
objList1.Add(objMem)
Next
End Sub
メモリ解放処理をしていなくてもメモリリークが発生しない理由は、TestList1 メソッドの呼び出しが完了した段階で、.NET 側でメモリ解放処理(ガベージコレクションの発動)を暗黙的におこなっているからです。
では、下記のように BtnListTest1 を 2 回押下した動作と同じロジックを実行した場合は、どうなるでしょう?
Private Sub BtnListTest1_Click(sender As Object, e As EventArgs) Handles BtnListTest1.Click
Call Me.TestList1()
End Sub
Private Sub TestList1()
Dim objList1 As New List(Of System.IO.MemoryStream)
Dim objList2 As New List(Of System.IO.MemoryStream)
'========== 約 1GB のメモリを消費 ==========
For i = 1 To 10
'約 100MB のオブジェクトを生成
Dim objMem As New System.IO.MemoryStream(100000000)
'========== 何かしらの処理(その1) ==========
objList1.Add(objMem)
Next
'========== 約 1GB のメモリを消費 ==========
For i = 1 To 10
'約 100MB のオブジェクトを生成
Dim objMem As New System.IO.MemoryStream(100000000)
'========== 何かしらの処理(その2) ==========
objList2.Add(objMem)
Next
End Sub
結果は、途中で Out Of Memoty のエラーで落ちますよね。
BtnListTest1 を 2 回押下した動作と同じ処理を行っているのに、何故、連続で同じロジックを実行するとエラーが発生するのでしょうか?
理由は、objList1 のメモリ解放処理を実施せずに、同一メソッド内で objList2 の生成でメモリ消費をしているためです。
BtnListTest1 を 2 回押下した場合は、TestList1 メソッドを 2 回呼び出していたので(暗黙的にメモリ解放を行っていたので)、問題なく動作しました。
これは悪い例なので、マネしないようにしてください。
メモリ解放を考慮したロジック
List クラスには、Dispose メソッドは存在しません。
では、Nothing をセットすればガベージコレクションの処理対象になるのかな?かな??
結論から言うと Nothing をセットするだけではダメです!!
VB.NET メモリリークによるメモリ解放の重要性(DataTable 編)でも、記載しましたが Clear メソッド実行後に Nothing をセットしましょう!
Private Sub BtnListTest1_Click(sender As Object, e As EventArgs) Handles BtnListTest1.Click
Call Me.TestList1()
End Sub
Private Sub TestList1()
Dim objList1 As New List(Of System.IO.MemoryStream)
Dim objList2 As New List(Of System.IO.MemoryStream)
'========== 約 1GB のメモリを消費 ==========
For i = 1 To 10
'約 100MB のオブジェクトを生成
Dim objMem As New System.IO.MemoryStream(100000000)
'========== 何かしらの処理(その1) ==========
objList1.Add(objMem)
Next
objList1.Clear()
objList1 = Nothing
'========== 約 1GB のメモリを消費 ==========
For i = 1 To 10
'約 100MB のオブジェクトを生成
Dim objMem As New System.IO.MemoryStream(100000000)
'========== 何かしらの処理(その2) ==========
objList2.Add(objMem)
Next
objList2.Clear()
objList2 = Nothing
End Sub
このように、使用しなくなったオブジェクトをガベージコレクションの処理対象にするには、Nothing をセットするだけではなく、リソースの解放(Dispose、Clear、Close など)をした後に、Nothing をセットするように心がけましょうね。