none
コントロールの生成と破棄でメモリリーク? RRS feed

  • 質問

  • お世話になっております。

     

    現在、プログラム内でコントロールの生成と破棄を繰り返す処理を行っているのですが、

    どうもメモリがリークしているように見えます。

     

    サンプルとして、下記のようにコントロールを生成して破棄しただけでもリークしているように見えます。

     

                while (true)
                {
                  Panel panel = new Panel();
                  panel.Dispose();
                }

     

    メモリの確認としては、パフォーマンスログで対象のプロセスの

    Page File Bytesで確認しています。

    ちなみに、プログラム内でGC.Collect()を呼んでGC.GetTotalMemory()で

    メモリ量を確認するとこちらは増えていません。

     

    どなたかわかる方がいましたら、ご教授の方お願い致します。

                                   

    2007年10月12日 6:12

回答

  • 本家の MSDN Forums の方でも類似した投稿がありました.恐らくこれと同じ現象だと思います.

    # GC Handles constantly raises

     

    この投稿によれば,Control が内部で使用している NativeWindows クラスが,コンストラクタで自分自身に対して弱い GCHandle を作成するようです.

    弱い GCHandle なので,ポイント先の NativeWindows インスタンスは GC によって回収されるのですが,このハンドルそのものは,意図してか意図せずか残り続けるようでした.

    結果的に Control 派生クラスのインスタンスを作るごとに,ハンドルのサイズだけメモリが消費されるようです.

     

    これで困るかどうかは場合によりけりで,(急いで) 修正してもらえるかどうかもその必要性に依存する話ですが,テクニカルサポートを通して NOPG さんの状況を説明し,対応を検討してもらうのがおすすめです.

     

    2007年10月19日 9:09
  • しばらく見てなかったら、またメンドクサそーな問題が (^^;

     

     NyaRuRu さんからの引用

    ライブラリ側の不具合であると断定できる状況であれば,Microsoft の有償サポートを受けるのが一番かと思います.場合によってはパッチを作ってもらって,早期に提供してもらえることもあります.

     

    僕も有償サポートに持ち込んだ方がいいと思います。

    MSDN Library サブスクリプションを購読されてるでしょうから、そのインシデントを使ってサポート契約を結んでください。

     

    ライブラリの(ふかーいところ)のバグであることはほぼ確定ぽいので、Microsoft 側でそれを認識すれば、インシデントは返ってきます。

     

    ただ、有償サポート契約を結んだからと言って、「必ず」ホットフィックスが出る保証はありません。

    ホットフィックスの作成を依頼できるのは、プレミアムサポート契約を結んでいる場合に限られます。

     

    # プレミアムサポート契約は、一般企業ではそうそう承認の出ないような費用がかかります。

     

    一般の有償サポート契約でホットフィックスが出てくるのは、大体、そういったプレミアムサポート契約を結んだところから既にホットフィックスの作成依頼があった場合か、Microsoft が自発的にホットフィックスを用意した場合だと思われます。

     

    サポートとと交渉する場合のコツは、開発の手間や効率云々よりも「ビジネス的に困る」という点を強調するとインパクトを認めてもらえる傾向があるようです。

     

    この場合、「最低数か月、出来れば1年程度は無停止で運用したいが、(このバグ?のせいで)出来ない」「これでは要件が満たせなくて困る」という形で伝えるのがいいと思います。

     

    2007年10月19日 12:14
    モデレータ
  •  NOPG さんからの引用

    大分遅くなりましたが、補足情報です。

    この件についてはMSもバグとの認識があるようです。

     

    http://support.microsoft.com/kb/939731/ja

     

    ホットフィックスがありますが、

    SPでの完全な修正を早急に望みたいところです。

     

    情報ありがとうございます.

    つい先日 .NET Framework 2.0 SP1 がリリースされているのですが,そちらで修正済みかもしれません.実際,手元の環境の System.windows.forms.dll はファイルバージョンが 2.0.50727.1433 となっていて,バグの症状が出なくなっていました.

     

    .NET Framework 2.0 SP1 については,単体での適用もできますし,より新しい .NET Framework の更新によって自動でインストールもされます.

    詳しくは『.NET Framework 3.5 と、.NET Framework 3.0および2.0 の関係』等をご覧下さい.

    ダウンロードはこちらから.→ .NET Framework ダウンロード

     

    また,ファイルバージョンと修正項目 (KB) の対応については,こちらで Microsoft の社員の方が個人でまとめられている一覧が参考になります.オフィシャルな文章ではありませんが,ご参考までに.(KB939731 はリストから漏れているようですが)

    Version history of the CLR 2.0

     

     

     

    2007年11月27日 3:40

すべての返信

  • メモリリークなんてしていないと思いますよ、まず絶対。

     

     NOPG さんからの引用

    メモリの確認としては、パフォーマンスログで対象のプロセスの

    Page File Bytesで確認しています。

    これを見ても、.Netがメモリリークしているかどうかなんて、わかりません。

    2007年10月12日 7:31
  • メモリを解放するのはガベージコレクタ (GC) です。

    そして、 Dispose() メソッドの呼び出しが完了した時点では、 GC はメモリを解放しません。

     

    GC がガベージコレクションを実施してメモリを解放するタイミングは、 GC 自身が判断します。

    強制的にガベージコレクションを行わせるのが、 GC.Collect() メソッドです。

     

    参考: MSDN ライブラリ .NET Framework 開発者ガイド 自動メモリ管理 など

    2007年10月12日 7:40
  • GC.Collect() は呼んでいます。

    わからないのは、その後のGC.GetTotalMemory()で返す値が

    安定しているのですが、対象のプロセスのPage File Bytesが増えている事が気になります。

    そのまま放置するとメモリの最大値付近でそのプロセスが落ちます。

    これは、マネージ領域のメモリはリークしていなが、アンマネージの領域はリークしていると

    言う事でしょうか?

     

    勘違いな事を聞いているかも知れませんが、ご教授をお願いします。

    2007年10月12日 8:34
  •  NOPG さんからの引用

    そのまま放置するとメモリの最大値付近でそのプロセスが落ちます。

    落ちているのなら、デバッグしましょう。
    2007年10月12日 8:50
  • 確かに、おかしいですね。Windows Forms か GDI+ のバグみたいですね。

    Windows Forms のソースコードが公開されたらそれも明らかになるでしょう。

     

    2007年10月12日 23:06
  •  Kazuya Ujihara さんからの引用

    確かに、おかしいですね。Windows Forms か GDI+ のバグみたいですね。

    すみません。何が、どう、おかしいのでしょうか?

    詳しく、ご説明していただきたいです。

    2007年10月13日 1:33
  • GC.Collect() を入れることが、MUST になっているようにみえます。

     

    コード ブロック

            private void Form1_Load(object sender, EventArgs e)
            {
                while (true)
                {
                    Panel panel = new Panel();
                    panel.Dispose();
                    //GC.Collect();
                }
            }

     

     

    を実行してみてください。

    2007年10月13日 10:22
  •  Kazuya Ujihara さんからの引用

    を実行してみてください。

    はい。既に、実行しています。

    現在、開始から、13時間(CPU時間)が経過していますが、別に落ちてはいません。

    メモリ使用量も、タスクマネージャで見る限り、約500MBを上限として、それ以上は増えないようです。

    ので、特に問題ないように見えます。

     

    Ujiharaさんのところでは、このコード、落ちているのでしょうか?

    落ちているのであれば、具体的にどんなふうに落ちているのでしょうか?(送出される例外の内容など)

     

    # Windowsフォームですよね?

    # Loadイベントから実行しないと、再現しないのかしらん?

     

     

    2007年10月13日 13:18
  • メモリを見るのでしたらパフォーマンスログで対象のプロセスのメモリ使用量を見てみてください。

    右肩上がりであがっているかと思います。

    ただし、上記のコードですとスペックにもよりますが、13時間ぐらいでは大して増えませんので

    メモリ使用量も最大値付近まである程度使用している状態で回してみてください。

     

    メモリ最大値付近まで行きますと突然消えます。

    (メモリ最大値付近でのアプリケーションの動作は不安定なので

    環境または、状態により動作が変わってくるかと思います。)

     

    自分の環境ですと、上限値はなかったです。

    ある程度のところで安定してくれるのでしたらいのですが、、

     

    尚、GC.Collect()は呼んでいます。

     

     

    2007年10月13日 14:44
  • 私の環境ではどんどんコミットチャージが上がっていきました。

    落ちるところまでは確認できてません。

     

    で、disposeするのに時間がかかるのではないですか?

    待機させればメモリ(タスクマネージャのパフォーマンスで見ることのできるコミットチャージ)は増えません。

     

    コード ブロック

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            While True
                Dim p As New Label
                p.Dispose()
                '待機方法
                'Dim i As Integer
                'For i = 0 To 10000
                '    Application.DoEvents()
                'Next

            End While
        End Sub

     

     

    私が試す限りThread.Sleepでも増えませんでした。

     

     

    2007年10月13日 15:59
  •  Kazuya Ujihara さんからの引用

    確かに、おかしいですね。Windows Forms か GDI+ のバグみたいですね。

    Windows Forms のソースコードが公開されたらそれも明らかになるでしょう。

     

    少しだけおかしい場合があるようです。

     

    Windows Server 2003のRDP環境では

    Panelの生成1回につき平均4byte程度メモリが消費されていきます。

    ですが、Vista UltimateではRDPでもConsoleでも問題ありませんでした。

     

    VistaでOKなのでマネージ側の問題ではない、つまりSystem.Windows.Forms.FormはOK。

    RDPでダメなのでディスプレイドライバということもなく。

    Shellかな?

     

    コントロール生成1回につき4byteを気にする用途っていうと、

    生成・消滅を連続で行いながら長時間起動させて置く場合ですね。

    入力がなくなったら生成・消滅をやめるような仕組みをいれないといけませんね。

     

    追記。

    O(n^2)の時間がかかるようですし、

    残りメモリが少なくなるとページ切り替えで時間を殆ど費やしてしまいますので、

    一見、消費メモリが増えないように見えます。

    物理メモリが2Gとかだと全部消費するには1日ぐらいかかるはずです。

    配列でも確保してメモリを圧迫しないとなかなか落とせません。

     

    それと、この場合はPage File Bytesでなく

    Private Bytesが適切だと思います。

    2007年10月13日 17:59
  • みなさん、ご回答ありがとうございます。

     

    現在、24時間365日稼動を目標とするアプリを作成中ですが、

    現状では上記の問題(リーク)をクリア出来そうにないので、

    連続運用は無理との結論になりそうです。。

     

     

    2007年10月17日 5:21
  •  NOPG さんからの引用

    現在、24時間365日稼動を目標とするアプリを作成中ですが、

    現状では上記の問題(リーク)をクリア出来そうにないので、

    連続運用は無理との結論になりそうです。。

     

    パッチの適用などもありますから、

    サーバーOSでも1、2ヶ月に1回程度リブートすることを前提に作られてると思います。

     

    例えば半年リブートしないとして、

    6ヶ月~1.6x10^7[sec] = 16[Msec]

    です。

    1秒に1回コントロールを作ったとしても

    16[Msec]/1[sec]*4[byte] = 64[Mbyte]

    半年で64[Mbyte]しか消費しません。

     

    これが問題になる用途って、どんな用途でしょう?

    例えば、ユーザー入力が無い時はコントロールの生成・破棄をやめたり、

    ソフトを2重に起動しておいて、定期的に交互に再起動することはできないのですか?

     

    もし本当に24時間365日コントロールの生成・破棄を繰り返していて1回もリブートできない、

    そんな状況なのだとしたら普通のPCを使うこと自体が間違いだと思うんですが。

    2007年10月17日 15:35
  • 最近の要望ではサーバーでなくとも連続運用を求められています。

    かつ公開しないLAN上でかつ組み込みに近いような用途のアプリですと基本的に放置状態で運用されたりします。

     

    現在の動作ですが、状態によって画面が遷移します。

    または、定期タスクとしても遷移させます。

    その際に、コントロールの生成と破棄を内部的に行っています。

    コントロール数も一つ二つではないのでリーク量もある程度になっています。

    (現状ですと、1日12MBぐらいになっています。)

     

    この問題を始めからわかっていたら、上記のような作りは避けたのですが、

    今となってはもう変更が効かないぐらいのボリュームです。

     

    365日は目標ですが、最低半年ぐらいは持って欲しいです。

     

     

     

    2007年10月18日 2:02
  •  NOPG さんからの引用
    この問題を始めからわかっていたら、上記のような作りは避けたのですが、

    今となってはもう変更が効かないぐらいのボリュームです。

     

    365日は目標ですが、最低半年ぐらいは持って欲しいです。

     

    ライブラリ側の不具合であると断定できる状況であれば,Microsoft の有償サポートを受けるのが一番かと思います.場合によってはパッチを作ってもらって,早期に提供してもらえることもあります.

     

    また,メモリリークが疑われていながら単にメモリ使用量のチェックしかされていないようですが,CLRProfiler, sos.dll などを使用すれば,ある瞬間に存在するマネージオブジェクトの種類・個数を調べることも可能です.

    また,カーネルオブジェクトのリークでは,Process Explorer 等を利用したハンドルリークの調査が有効でしょう.

    このまま有償サポートに持ち込むのもありだとは思いますが,どこでリークしているのか調べるのであれば,まだまだ調べられる点はたくさんあると思いますよ.

     

    cf. OutOfMemoryの原因調査方法について

     

    (追記) 手元の環境 (Windows Vista Japanese Edition, x86) で試してみましたが,GC Handles が増え続けているようですね.パフォーマンスカウンタで確認してみてください.

    GC Heap のメモリ使用量は 定期的な GC によって一定水準に保たれているようです.

     

    GC Handle のリーク解析ですが,定番資料としては『Mini Dump Snapshots and the New SOS』あたりでしょうかね.

    2007年10月18日 2:45
  • ご教授ありがとうございます。

     

    対象のプロセスのメモリ使用量やハンドル数などしか見ていませんでしたので

    他の値も見てみます。

    CLRProfiler等での確認も行ってみます。

    時間が許す限りは追いたいと思います。

     

    同時にMicrosoft の有償サポートも検討致します。

     

    2007年10月19日 5:01
  • 実際にしばらく動かしてみました。

    メモリの使用量の増加は大したことありませんでしたが、パフォーマンスの低下は看過できないレベルでありました。

    2007年10月19日 8:13
  • 本家の MSDN Forums の方でも類似した投稿がありました.恐らくこれと同じ現象だと思います.

    # GC Handles constantly raises

     

    この投稿によれば,Control が内部で使用している NativeWindows クラスが,コンストラクタで自分自身に対して弱い GCHandle を作成するようです.

    弱い GCHandle なので,ポイント先の NativeWindows インスタンスは GC によって回収されるのですが,このハンドルそのものは,意図してか意図せずか残り続けるようでした.

    結果的に Control 派生クラスのインスタンスを作るごとに,ハンドルのサイズだけメモリが消費されるようです.

     

    これで困るかどうかは場合によりけりで,(急いで) 修正してもらえるかどうかもその必要性に依存する話ですが,テクニカルサポートを通して NOPG さんの状況を説明し,対応を検討してもらうのがおすすめです.

     

    2007年10月19日 9:09
  • しばらく見てなかったら、またメンドクサそーな問題が (^^;

     

     NyaRuRu さんからの引用

    ライブラリ側の不具合であると断定できる状況であれば,Microsoft の有償サポートを受けるのが一番かと思います.場合によってはパッチを作ってもらって,早期に提供してもらえることもあります.

     

    僕も有償サポートに持ち込んだ方がいいと思います。

    MSDN Library サブスクリプションを購読されてるでしょうから、そのインシデントを使ってサポート契約を結んでください。

     

    ライブラリの(ふかーいところ)のバグであることはほぼ確定ぽいので、Microsoft 側でそれを認識すれば、インシデントは返ってきます。

     

    ただ、有償サポート契約を結んだからと言って、「必ず」ホットフィックスが出る保証はありません。

    ホットフィックスの作成を依頼できるのは、プレミアムサポート契約を結んでいる場合に限られます。

     

    # プレミアムサポート契約は、一般企業ではそうそう承認の出ないような費用がかかります。

     

    一般の有償サポート契約でホットフィックスが出てくるのは、大体、そういったプレミアムサポート契約を結んだところから既にホットフィックスの作成依頼があった場合か、Microsoft が自発的にホットフィックスを用意した場合だと思われます。

     

    サポートとと交渉する場合のコツは、開発の手間や効率云々よりも「ビジネス的に困る」という点を強調するとインパクトを認めてもらえる傾向があるようです。

     

    この場合、「最低数か月、出来れば1年程度は無停止で運用したいが、(このバグ?のせいで)出来ない」「これでは要件が満たせなくて困る」という形で伝えるのがいいと思います。

     

    2007年10月19日 12:14
    モデレータ
  • NyaRuRuさん、ありがとうございます。

     

    同じ現象かと思います。

    原因については、行き詰っていましたので

    大変参考になります。

    自分の技術と知識ではここまでは行き着かないと

    思うだけに頭が下がります。

     

    これから、運用面での検討とMicrosoft のサポート面での結果で

    どうするのかの検討に入りたいと思います。

    2007年10月22日 5:51
  • 渋木さん、ご教授ありがとうござます。

     

    テクニカルサポートとのお付き合いはあまりないので

    参考にさせて頂きます。

     

    皆様のご教授には唯唯感謝しております。

    2007年10月22日 5:58
  • 大分遅くなりましたが、補足情報です。

    この件についてはMSもバグとの認識があるようです。

     

    http://support.microsoft.com/kb/939731/ja

     

    ホットフィックスがありますが、

    SPでの完全な修正を早急に望みたいところです。

    2007年11月27日 3:03
  •  NOPG さんからの引用

    大分遅くなりましたが、補足情報です。

    この件についてはMSもバグとの認識があるようです。

     

    http://support.microsoft.com/kb/939731/ja

     

    ホットフィックスがありますが、

    SPでの完全な修正を早急に望みたいところです。

     

    情報ありがとうございます.

    つい先日 .NET Framework 2.0 SP1 がリリースされているのですが,そちらで修正済みかもしれません.実際,手元の環境の System.windows.forms.dll はファイルバージョンが 2.0.50727.1433 となっていて,バグの症状が出なくなっていました.

     

    .NET Framework 2.0 SP1 については,単体での適用もできますし,より新しい .NET Framework の更新によって自動でインストールもされます.

    詳しくは『.NET Framework 3.5 と、.NET Framework 3.0および2.0 の関係』等をご覧下さい.

    ダウンロードはこちらから.→ .NET Framework ダウンロード

     

    また,ファイルバージョンと修正項目 (KB) の対応については,こちらで Microsoft の社員の方が個人でまとめられている一覧が参考になります.オフィシャルな文章ではありませんが,ご参考までに.(KB939731 はリストから漏れているようですが)

    Version history of the CLR 2.0

     

     

     

    2007年11月27日 3:40
  •  

    またまた返信が遅くなりまいた。

     

    MSに問合せをしたところ、

    SP1でこのバグ(KB939731)は改修されたとの事です。

    NyaRuRuさん、いろいろと情報をありがとうございます。

     

    常々感謝致します。

    ありがとうございました。

    2007年12月5日 4:10