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

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


VB.NET メモリリークによるメモリ解放の重要性(MemoryStream 編)


【スポンサーリンク】


みなさんアプリケーションを開発している際、メモリリークって気にしてますか?
私は、小規模のクラサバ、且つ、非常駐アプリの場合は、正直それほど気にしていません。
しかし、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 回呼び出していたので(暗黙的にメモリ解放を行っていたので)、問題なく動作しました。

これは悪い例なので、マネしないようにしてください。

 

 
メモリ解放を考慮したロジック

では、メモリリークが発生したロジックに、メモリ解放処理を入れてみましょう!

  1. 使用済みになった後に、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 のセット : ガベージコレクションの処理対象とするためのセット

  2. 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 編)もあわせて読んでみてもらえればと思います。


Visual Basicの絵本 Windowsプログラミングわかる9つの扉