中年システムエンジニアのオモチャ箱

中年システムエンジニアが初体験のブログ活動。技術情報の備忘録以外も、色々と載せていければと思います。

【スポンサーリンク】


VB.NET メモリリークによるメモリ解放の重要性(ジェネリックコレクション編)


【スポンサーリンク】


以前、メモリリーク対策について MemoryStream、DataTable の解放方法を記載しました。

 

www.ma-se.com

www.ma-se.com

今回は、頻繁に使うであろうジェネリックコレクションの 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 をセットするように心がけましょうね。