メモリリークについて

解決


きたきつね  2004-05-28 15:12:18  No: 83614  IP: [192.*.*.*]

こんにちわきたきつねです。
VBでのメモリリークについてインターネット上で調べているのですが、C++についてのメモ
リリークの記事はたくさんありましたが、VBのほうで自分が納得いくまで理解できる記事
がありませんでした。
それでも自分なりに調べた結果こうなのです。(使用言語はVB6です)

1.オブジェクト型の変数を宣言し、インスタンスを作成代入して使用後に開放しな
  かった時。

    Dim cnn As New ADODB.Connection
    Dim rst As New ADODB.Recordset
    
    ' 接続を確立する
    cnn.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _
             "Data Source=C:\My Documents\db1.mdb;"
             
    ' テーブル名“Table1”のレコードセットを作成する
    rst.Open "Table1", cnn, adOpenKeyset, adLockOptimistic

    'この後cnn,rstを開放せずに(Set rst = Nothingしないで)処理を終了



2.最初にオブジェクト型変数を宣言し、インスタンスを作成代入。そして最初のイ
  ンスタンスを開放せずに同じ変数に次のインスタンスを作成代入した時、最初のイ
  ンスタンスがメモリリークする。

    Dim cnn As New ADODB.Connection
    Dim rst As New ADODB.Recordset
    
    ' 接続を確立する
    cnn.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _
             "Data Source=C:\My Documents\db1.mdb;"
             
    ' テーブル名“Table1”のレコードセットを作成する
    rst.Open "Table1", cnn, adOpenKeyset, adLockOptimistic

    ' 上のインスタンスを開放せずにテーブル名“Table2”のレコードセットを作成する
    rst.Open "Table2", cnn, adOpenKeyset, adLockOptimistic
    '↑この時Table1のレコードセットはメモリリーク?


以上の2点はC++やJavaについてのサイトを参考にしたのですがメモリリークする時
はこの2点だけでいいのでしょうか。C++などの考え方と同じでいいのでしょうか。
どうかVBでのメモリリークについてご教授ください。

編集 削除
岡田 之仁  2004-05-28 16:13:41  No: 83615  IP: [192.*.*.*]

VBの場合のメモリリークは、モジュールやコンポーネント内の
ものの方が多く、コーディング的に発生するものより厄介だと
思います。(参照設定するものや、ActievXとかも・・・)

メモリリークを追いかけたいのであれば、VB用の各種ソフトを
販売されている、日本コンピュウェア社の、DevPartnerStudio
とかをお使いになった方が、確実に捕まえられると思います。

ご参考までに・・・

以上。

編集 削除
GOD  2004-05-28 16:14:29  No: 83616  IP: [192.*.*.*]

> メモリリークする時はこの2点だけでいいのでしょうか。
2点だけということは無いです。プログラムの書き方で点数は増えるでしょうから。

> C++などの考え方と同じでいいのでしょうか。
どういう状態を「メモリリーク」というのか一度用語集などで調べて下さい。

↓用語集:「メモリリーク」
_ttp://yougo.ascii24.com/gh/26/002659.html

編集 削除
きたきつね  2004-05-28 18:39:22  No: 83617  IP: [192.*.*.*]

はやいスレありがとうございます。あまりの速さに感動です。

岡田 之仁へ
>モジュールやコンポーネント内のものの方が多く
どのような時発生するのですか?お手数ですが少しだけ教えてください。もし内容
が複雑で記述がご面倒なら何か参考になるサイトや書籍などあったら教えてくださ
い。

>日本コンピュウェア社の、DevPartnerStudioとかをお使いになった方が、確実に
>捕まえられると思います。
日本コンピュウェア社のサイトに無料で配っているDevPartner Profiler Community
 Editionがありました。家に帰ったら早速ダウンロードしようと思います。(今は会社です。こっそり)


GODさんへ
>2点だけということは無いです。プログラムの書き方で点数は増えるでしょうから。
自分の調べた結果の2点は考え的に間違ってはいないですか?間違っていなければう
れしいです。

>どういう状態を「メモリリーク」というのか一度用語集などで調べて下さい。
さっそく調べてみました。この内容からするとどの言語でもオブジェクトを開放し
なければ共通に起こることなのですね(JAVAは起こらないみたいです)。理解しま
した。

編集 削除
きたきつね  2004-05-28 18:42:03  No: 83618  IP: [192.*.*.*]

岡田 之仁さん申し訳ありません。
>岡田 之仁へ
岡田 之仁さんへと書いたつもりが呼び捨てになってしまって。
親切で教えてくれたのに本当に申し訳ないです。
すみません、、

編集 削除
岡田 之仁  2004-05-28 20:44:11  No: 83619  IP: [192.*.*.*]

DevPartner に含まれるBounds Checker 等で、例えば・・・
ExcelやAccessを開いて、何がしか操作した後、閉じて下さい。

Bounds Checker に、山のようなログが出ます。
メモリリークもかなりあります。

別段、難しいことは自作の怪しいプログラムやスクリプトを
書かないでも、勝手にリークします。

要はそのことを言っています。

で、その怪しいプラットフォームの上で動作するソフトを作成
しますので、コーディング的には、できるだけダメージを出し
たくないので、リークするような部分は徹底的に修正したいで
す。

そういう意味では、そのようなツールは有用です。

故意や誤ってリークしてしまうコードは書けますが、システム
的に提供されるソフトの内部でリークが発生しますので、それ
はいかんともしがたいです・・・

より不安定にならないように気をつける必要は十分にあると思
います。

※  現在のOffice System等でどうなのかについては言及して
    いません。少々昔のOfficeでは・・・あったということで
    す。

以上。

編集 削除
きたきつね  2004-05-29 12:36:58  No: 83620  IP: [192.*.*.*]

岡田 之仁さんへ
さっそくDevPartner Profiler Community Editionをダウンロードしました。
いざインストールしようとしたところVisual Studil.Netを先にインストールしてく
ださい!!と言われました。わたしは、VB.NetとVB6.0は持っているのですがVisual 
Studio.Netをもっていないのでインストールできませんでした。(とほほ、、)
しかしこの掲示板でのスレは自分にとってまた一歩進むことができました。

>Bounds Checker に、山のようなログが出ます。
本当に有用なツールだと思います。なんとしても手に入れたい!!会社に買ってくれ
って何とか説得できないものかな?

>別段、難しいことは自作の怪しいプログラムやスクリプトを
>書かないでも、勝手にリークします。
コーディング以外にもこんなことがあるなんてぜんぜん知りませんでした。メモリリ
ークについてせめてコーディング上では細心の注意が必要だと思い知らされました。
また一つ自分の知識が上がってうれしいです。

話は戻ってしまうのですが、最初に自分が質問した時の
>2.最初にオブジェクト型変数を宣言し、インスタンスを作成代入。そして最初のイ
>  ンスタンスを開放せずに同じ変数に次のインスタンスを作成代入した時、最初のイ
>  ンスタンスがメモリリークする。
この部分が本当にそうなのか自信がなく、もっとも気になっているところです。この
部分は合っているのでしょうか?“間違ってない”とタイコバンを押してもらえれば
メモリリークの疑問は晴れて解決です。
疑問にこたえてくださって本当にありがとうございます。

編集 削除
NOK  2004-05-29 12:50:46  No: 83621  IP: [192.*.*.*]

クラスモジュールを作って試してみてください。たとえばClass1という
クラスを作り、そこのClass_InitializeイベントとClass_Terminate
イベントで何かメッセージを表示させると分かると思います。

別のフォームにボタンを貼り付けそこで
Dim objA As Class1

Set objA = New Class1
Set ojbA = New Class1

というコードを実行させると
Initialize及びTerminateイベントが2回ずつ発生します。
ということは上記のような使い方をしてもクラスモジュール内で
適切な処理が行われていればメモリリークしないようにできている
ということだと思います。

編集 削除
ねろ  2004-05-29 14:30:03  No: 83622  IP: [192.*.*.*]

きたきつねさんのコードではメモリーリークは起き無いと思います。
VBの場合メモリーの解放は、 オブジェクトのインスタンスの
参照が無くなった時にされるとのことです。
同じ変数に再び新しいインスタンスを作成すると、前のインスタンスは
参照が終わったとして破棄されます。
Set rst = Nothing
としたにもかかわらずメモリーが解放されないのがメモリーリークです。
実際は参照カウンターと言うのを使っているようです。
複雑な循環参照を行って参照カウンターが誤動作した時やOCXにバグが
あったりする時に起きます。
VBではCと違って簡単にメモリーリークするプログラムを書く事は
出来ません。
詳しくは「参照カウンター  VB」などとして検索してみて下さい。

編集 削除
魔界の仮面弁士  2004-05-29 14:33:26  No: 83623  IP: [192.*.*.*]

Nothingすればリークしない、という物でもありませんし、逆に
Nothingしなければリークする、というわけでもありません。


オブジェクトは、内部的に「参照カウント」という物を持っています。

インスタンスは、「Set X = New Class1」などと変数等に参照された時に、
参照カウントが増加します(IUnknown.AddRef)。そして解放されると、
参照カウントは逆に減少するようになっています(IUnknown.Release)。

そしてカウントが 0 になった場合、どこからも参照されなくなったとされ、
そのオブジェクトが使用していたメモリも解放される仕組みになっています。


この仕組みがあるため、建前上は、

・Nothingせずとも、変数がスコープ外になって破棄されれれば、
  その参照カウントも現象する。

・Nothing前に別のオブジェクトをセットした場合、元のインスタンスの
  参照カウントは、Nothingした時と同様に減らされる。

・そして、すべての参照が解放された(参照カウント=0)だった場合、
  そのオブジェクトが使っていたメモリも破棄される。

という動作になるように設計されています。


しかし、CloseメソッドやDisposeメソッドなど、解放する前に呼び出すべき
メソッドを呼んでいなかった場合は、オブジェクトは解放されても、それが
内部的に使っていたリソースの解放に問題が起きるケースは想定できます。

例えば、KB 289562などがこれに相当します。
  『データベースの不要な拡張を防ぐにはレコードセットを明示的に閉じる』
  http://support.microsoft.com/?kbid=289562


また、そのオブジェクトの作りに問題があって、循環参照などを
起こしていて、Nothingするだけでは解放されないケースもありえます。
  http://www.microsoft.com/japan/developer/library/VBCon98/vbconcircularreferencesobjectlifetime.htm

また、状況によっては「あえてNothingをしない」という運用もあります。

最初の例にあげられたADOなどの場合、「プーリング」という仕組みが
あるのですが、それを(MTSやASPを使わずに)利用しようとした場合、
プール用の接続オブジェクトと、実際に使う接続オブジェクトを
2本用意して、プール用は「OpenしてすぐClose、でもNothingしない」
実際に使う方は「Open/Close/Nothingを通常通り行う」という
コーディングが行われる事があるからです。

# まぁ、アプリ終了時にはプール用オブジェクトもNothingするわけですけど。


>> モジュールやコンポーネント内のものの方が多く
> どのような時発生するのですか?
DLL内部のバグなどによるものも考えられます。

上記のメモリの話は、COM(ActiveX)がらみのオブジェクトの話であって、
API等にてメモリの確保と解放を行った場合は、自動的に解放されません。

なので、コンポーネント内部でバグがあれば、コンポーネント自身を
解放しても、それが使っていたリソースが残ってしまう事はありえます。
こうなると、VB側では手の施しようが無いかも知れません。

古いVBほど、VB自身が引き起こすリークも多かったですし。
http://support.microsoft.com/search/default.aspx?InCC_hdn=True&InMT_hdn=true&QuerySource=gASr_Query&Catalog=LCID%3D1041%26CDID%3DJA-KB%26PRODLISTSRC%3DON&Product=vbJPN&Queryc=%E3%83%AA%E3%83%BC%E3%82%AF&Query=%E3%83%AA%E3%83%BC%E3%82%AF&KeywordType=ALL&maxResults=150&Titles=false&numDays=&InCC=on&InMT=on

編集 削除
きたきつね  2004-05-30 21:07:34  No: 83624  IP: [192.*.*.*]

NOKさんへ
>クラスモジュールを作って試してみてください。たとえばClass1という
>クラスを作り、そこのClass_InitializeイベントとClass_Terminate
>イベントで何かメッセージを表示させると分かると思います。
実際に試した見ました。実際にInitializeイベントとTerminateが2回づつ発生するこ
とを確認しました。Cとは違うことにおどろきました。


ねろさんへ
>実際は参照カウンターと言うのを使っているようです。
おはずかしながら参照カウンターの存在を知りませんでした。VBってなかなかやるじ
ゃん!ってあらためて見直しました。

>複雑な循環参照を行って参照カウンターが誤動作した時やOCXにバグが
>あったりする時に起きます。
勉強になります。

>詳しくは「参照カウンター  VB」などとして検索してみて下さい。
このキーワードで検索したらたくさんのためになるサイトがありました。あとでじっ
くり勉強しようと思います。


魔界の仮面弁士さんへ
>『データベースの不要な拡張を防ぐにはレコードセットを明示的に閉じる』
>http://support.microsoft.com/?kbid=289562
今まで自分はレコードセットを閉じずに“Set RST = Nothing”をしていました。

>また、そのオブジェクトの作りに問題があって、循環参照などを
>起こしていて、Nothingするだけでは解放されないケースもありえます。
>http://www.microsoft.com/japan/developer/library/VBCon98/vbconcircularreferencesobjectlifetime.htm
循環参照について例題つきですぐに理解することが出来ました。

>古いVBほど、VB自身が引き起こすリークも多かったですし。
>http://support.microsoft.com/search/default.aspx?InCC_hdn=True&InMT_hdn=true
>&QuerySource=gASr_Query&Catalog=LCID%3D1041%26CDID%3DJA-KB%26PRODLISTSRC%3DO
>N&Product=vbJPN&Queryc=%E3%83%AA%E3%83%BC%E3%82%AF&Query=%E3%83%AA%E3%83%BC%
>E3%82%AF&KeywordType=ALL&maxResults=150&Titles=false&numDays=&InCC=on&InMT=on
マイクロソフトサイトでリークをキーワードにすると情報が山ほど出てくることにび
っくりです。これもあとでじっくり勉強させていただきます。
他詳しい説明ほんとためになります。


みなさんへ
岡田 之仁さん、GODさん、NOKさん、ねろさん、魔界の仮面弁士さん、みなさんのお
かげでメモリリークに対する疑問が解けました。まだまだ勉強することは多いですが、
調べていく糸口が具体的にわかりました。今回メモリリークだけでなくそれ以上のも
のをえることが出来ました。ほんとうにありがとうございます。

編集 削除