tagCANDY CGI VBレスキュー(花ちゃん) の Visual Basic 2010 用 掲示板(VB.NET 掲示板) [ツリー表示へ]   [Home]
一括表示(VB.NET VB2005)
タイトルクラスの破棄について
記事No8927
投稿日: 2009/05/12(Tue) 11:54
投稿者ダンボ
(1枚の画像)または(100枚程度の画像セット)毎に1つの管理クラス(ProfileClass)
を割り当てているアルバム管理ソフトでの話です。メモリ使用量は1クラスあたり
・定常時56Kバイト
・画像操作時12.3Mバイト
と見積もりました。全画像は4,000枚程度あり、一気にすべてクラスを割り当てても
240Mバイト程度のメモリ使用量で済むという計算です。実際は管理不要になるたび
にProfileClassを破棄するので定常時は500枚分30Mバイト程度で行けるかと思っていました。

Public Class ProfileClass(主要メンバのみ)
    Public Event Updated(ByVal sender As Object, ByVal e As PFEventArgs)
    Protected NextPr As ProfileClass    'Nextクラス(スライドショーのとき使う)
    Public Cache As CacheClass          'メモリが許す限り破棄せずに再利用する
    Public frm As Form                  '表示に使うフォーム
    Public Super As ProfileClass        '上位クラス
    Public Contents As New System.Collections.Generic.List(Of ProfileClass)   '子クラス(100個程度)のプロファイル
    Public SelectedIndex As Integer     '最後にアクセスした子クラスのインデックス
    Public DefFileFullPath As String    '定義ファイルor画像ファイルの場所
    Public Category As UInteger         '画像分類
    Public Title As String              '画像タイトル
    Public Description As String        '画像説明
    Public Icon As Image                '画像サムネール(56KB程度)
    Public Image As Image               '画像本体(12.3MB程度)
    Public CreateDate As Date
    Public ModifyDate As Date
    Public AccessDate As Date
End Class

ところが、試験するとメモリ使用量が右肩上がりで増え続け1Gバイト超です。
ProfileClass.Finalize にトレースを入れてみるとSub Main()を抜けた後に
たくさんのProfileインスタンスが破棄されていました。orz

ProfileClassの破棄はProfile = Nothing
ProfileClass.Imageの破棄はProfile.Image.Dispose()、 Profile.Image = Nothing
でO.K.かと思ったんですが違うんですか?

http://msdn.microsoft.com/ja-jp/library/fs2xkftw(VS.80).aspx
http://www.atmarkit.co.jp/fdotnet/dotnettips/027dispose/dispose.html
などを見て、ProfileClass.Disposeメソッドを実装し、
  Profile.Dispose()
  Profile = Nothing
を実行したところ、メモリ使用量の右肩上がりは止まりSub Main後のFinalizeも
無くなったのでProfileClassの破棄は設計どおりになったと思います。

質問1.下記の認識(思い込み)の正誤を教えてください。
・Disposeメソッドが用意されていないクラスの破棄は = Nothing だけで良い。
・New()で生成したクラスはマネージドクラスである。
・マネージドクラスの破棄は = Nothing だけで良い。
・Finalizeメソッドを呼んだだけではクラスの破棄は行われない。
・Finalizeメソッドはシステムから呼ばれるだけのものであり、APPから呼ぶことはあまり無い。
 (Newとの対比から、Newで生成/Finalizeで破棄と思い込んでいました)

質問2.
  Profile.Dispose()
  Profile = Nothing
のようにDisposeを実行した後での = Nothingは無意味ですよね?

[ツリー表示へ]
タイトルRe: クラスの破棄について
記事No8928
投稿日: 2009/05/12(Tue) 12:16
投稿者Hongliang
> 質問1.下記の認識(思い込み)の正誤を教えてください。
> ・Disposeメソッドが用意されていないクラスの破棄は = Nothing だけで良い。
その変数からのインスタンスへの参照を切っているだけで、
インスタンス自体の破棄にはなりません(そもそもマネージドオブジェクトは
明示的にインスタンスを破棄することはできません)。
そのインスタンスがほかから参照されていなければ、そのうち GC が片付けます。
大抵の場合、Nothing の代入に意味はありません。
メソッド内のローカル変数ならメソッドを抜けたときに自動的に参照が無くなります。
IDisposable でないクラスのインスタンスは、「何もしない」が正しい方法です。
// まあクラス次第では「これ呼び出さないと動作保証しないよ」なんてのもあるかも知れませんが。

> ・New()で生成したクラスはマネージドクラスである。
そうですね。

> ・マネージドクラスの破棄は = Nothing だけで良い。
二つ上のが回答です。

> ・Finalizeメソッドを呼んだだけではクラスの破棄は行われない。
> ・Finalizeメソッドはシステムから呼ばれるだけのものであり、APPから呼ぶことはあまり無い。
>  (Newとの対比から、Newで生成/Finalizeで破棄と思い込んでいました)
そもそも Finalize  メソッドは明示的に呼び出せません。
フレームワークから呼び出されるだけのものです。
また大抵のクラスでファイナライザを実装する必要はありません。
直接アンマネージドリソースを持っているクラスのみがファイナライザを実装するぐらいです。
// .NET 2.0 からはハンドルをマネージドオブジェクトとしてラップできるクラスが用意されたため、
直接アンマネージドリソースを保持することもほとんど無くなりました。

> 質問2.
>   Profile.Dispose()
>   Profile = Nothing
> のようにDisposeを実行した後での = Nothingは無意味ですよね?
上でも答えましたが、Dispose を呼ぼうが呼ぶまいが Nothing の代入は大抵無意味です。

[ツリー表示へ]
タイトルRe^2: クラスの破棄について
記事No8929
投稿日: 2009/05/12(Tue) 13:57
投稿者ダンボ
Hongliangさん、ご回答有難うございます。2、3確認を。


>> ・マネージドクラスの破棄は = Nothing だけで良い。
  →「なにもする必要が無い。参照が無ければいつか破棄される。不要な参照を消して
  おくだけでよい。」と理解しました。


>インスタンス自体の破棄にはなりません(そもそもマネージドオブジェクトは
> 明示的にインスタンスを破棄することはできません)。
  →今回実験したように、Disposeメソッドを実装して呼べば明示的にインスタンスを
   破棄することが可能ですよね?


>そのインスタンスがほかから参照されていなければ、そのうち GC が片付けます。
  →Profileインスタンスが参照されていないことを大分しつこく確認して
   GC.Collect()を呼んでも回収されなかったのに、Profile.Disposeメソッドを実装して
   Dispose()を呼んだらメモリ増加が止まったのは何故でしょうか?
   Dispose()によって強制的にProfileインスタンスが破棄されたと思えるのですが。

>そもそも Finalize  メソッドは明示的に呼び出せません。
    →Implements IDisposable後はFinalizeメソッドを呼び出せました。
   もっともAPPから呼んでも意味が無いと感じました。
     フレームワークから呼び出されてアンマネージドリソースの解放忘れや
   その他インスタンス破棄時にどうしてもしておきたいことを記述するためにあるのですね。
   (それならFinalize「イベント」のほうが理にかなっているが?)

私の作成したProfileClassはマネージドクラスですか?
New()で作成すること、Disposeメソッドが無いこと、.NET Frameworkのクラスライブラリしか
使っていないのでマネージドクラスだと思えるが、そうすると
・Profileインスタンスの参照を切ってGC.Collect()を呼ぶ→回収されず
・Profile.Disposeメソッドを実装してDispose()を呼ぶ→回収された
という現象が納得いかないです。

[ツリー表示へ]
タイトルRe^3: クラスの破棄について
記事No8930
投稿日: 2009/05/12(Tue) 14:46
投稿者Hongliang
> >インスタンス自体の破棄にはなりません(そもそもマネージドオブジェクトは
> > 明示的にインスタンスを破棄することはできません)。
>   →今回実験したように、Disposeメソッドを実装して呼べば明示的にインスタンスを
>    破棄することが可能ですよね?
いいえ、インスタンスは破棄されていません。
そのインスタンスが持っているアンマネージなリソースは破棄されます。

> >そのインスタンスがほかから参照されていなければ、そのうち GC が片付けます。
>   →Profileインスタンスが参照されていないことを大分しつこく確認して
>    GC.Collect()を呼んでも回収されなかったのに、Profile.Disposeメソッドを実装して
>    Dispose()を呼んだらメモリ増加が止まったのは何故でしょうか?
>    Dispose()によって強制的にProfileインスタンスが破棄されたと思えるのですが。
Image の参照が流出してたとか考えられますが、
いずれにせよ何をどうお書きになったのかわからないのでなんとも。
// ミニマムなソースで動作確認することをおすすめします。
Dispose は所詮ただのメソッドです。特別視されることはありません。
IDisposable は「早めに片付けた方がいいブツを持ってるから
Using 構文使えるようにするね」程度の意味しかありません。

> >そもそも Finalize  メソッドは明示的に呼び出せません。
>     →Implements IDisposable後はFinalizeメソッドを呼び出せました。
>    もっともAPPから呼んでも意味が無いと感じました。
>      フレームワークから呼び出されてアンマネージドリソースの解放忘れや
>    その他インスタンス破棄時にどうしてもしておきたいことを記述するためにあるのですね。
>    (それならFinalize「イベント」のほうが理にかなっているが?)
これは私の理解不足でした。申し訳ありません。VB の場合任意に Finalize を呼べるんですね。
といっても、任意に呼び出した Finalize も所詮ただのメソッドです。特別なものではありません。
// まあその後そのオブジェクトを触るのは怖くてできませんし、
// そもそも任意に呼び出す意味はないでしょうが。

> 私の作成したProfileClassはマネージドクラスですか?
というかアンマネージなクラスは VB で作ることはできません。
VB のクラスはすべてマネージです。

[ツリー表示へ]
タイトルRe^3: クラスの破棄について
記事No8934
投稿日: 2009/05/12(Tue) 18:06
投稿者YuO
> ・Profileインスタンスの参照を切ってGC.Collect()を呼ぶ→回収されず
> ・Profile.Disposeメソッドを実装してDispose()を呼ぶ→回収された
> という現象が納得いかないです。

ファイナライザが存在する場合,
最初のガベージコレクトにおいてファイナライザの呼び出しがなされ,
2度目のガベージコレクトによってメモリが回収されます。
このため,ファイナライザが存在する場合,
第1世代のガベージコレクトが行われないとメモリは回収されませんし,
都合2度GC.Collectを呼び出さないとメモリは回収されません。

さらに,世代が上がるため,回収の行われる可能性が低くなっています。

通常のDisposeの実装では,GC.SuppressFinalizeの呼び出すことにより,
上記の動作を抑制します。
つまり,Disposeの呼び出しにより第0世代のガベージコレクトにおいて
メモリが回収されるようになります。

[ツリー表示へ]
タイトルRe^4: クラスの破棄について
記事No8937
投稿日: 2009/05/13(Wed) 10:26
投稿者ダンボ
Hongliangさん、YuOさん、どうも有り難うございます。
まとめレスで失礼します。

http://msdn.microsoft.com/ja-jp/library/fs2xkftw(VS.80).aspx
http://www.atmarkit.co.jp/fdotnet/dotnettips/027dispose/dispose.html
>などを見て、ProfileClass.Disposeメソッドを実装し、

「実装」など口はばったいこと書きましたが、実際は理解しないでコピペでした。
コメント部分に適切な処理を入れない限り殆ど意味の無い実装では?
本当にこのDispose()を実行したことが理由で、メモリ消費の右肩上がりが改善
されたのか自信がなくなってきました。(それでもSub Main()抜けた後での
Profile.Finalizeでのトレースが出なくなったのはDispose()の所為なんでしょう)

' Field to handle multiple calls to Dispose gracefully.
Dim disposed As Boolean = False

Public Overloads Sub Dispose() Implements IDisposable.Dispose
    Dispose(True)
    GC.SuppressFinalize(Me)
End Sub

Protected Overridable Overloads Sub Dispose(ByVal disposing As Boolean)
    If disposed = False Then
        If disposing Then
            ' Free other state (managed objects).
            disposed = True
        End If
    End If
    ' Free your own state (unmanaged objects).
    ' Set large fields to null.
End Sub

Protected Overrides Sub Finalize()
    Trace.WriteLine(DefFileFullPath, "PF:Finalize")
    Dispose(False)
End Sub


>を実行したところ、メモリ使用量の右肩上がりは止まりSub Main後のFinalizeも
>無くなったのでProfileClassの破棄は設計どおりになったと思います。

メモリ消費の一方的な右肩上がりは改善されてノコギリ状に増減するようになったものの、
・大量の画像処理実行中には、だんだんベースが上がっている。Sub Main後に一気に下がる
・ノコギリ状のメモリ増加量がProfileClassの見積量より大きいような気がする
→ProfileClass以外の解放忘れがあるのか?
メモリ消費のベース増加量がMB単位(10〜)である
画像Image以外にMB単位のメモリを消費するものを使っていない
→画像Imageの解放忘れの可能性が一番高いか
→画像Iconを作成するために一度に100個程度の画像Imageを読んではDispose()しているが

というような感じでProfile.Imageをキーワードにして処理ルーチンを見直します。

[ツリー表示へ]