1

メンテナンスを任されたイベント駆動型アプリがあります。約 100 のイベントが 30 秒ごとに別のタイマーで実行されます。時間の経過とともに、イベントは 1 秒あたり約 1 ~ 3 イベントの一定のストリームにエイリアスされます。メモリ使用量は、特定の 1 秒間に発生するイベントの数に依存しているわけではありません。各イベントは Web サービスからデータをポーリングし、LINQ2SQL DataContext を使用して以前にポーリングされたデータに対してデータをチェックし (完了時に DataContext を破棄または null アウトしません)、データが異なる場合は、データベースを更新し、新しいデータを次のようにプッシュします。 TCP 経由の受信者サービスへの XML メッセージ。

このアプリにはメモリ リークがあるようです。

  1. 30m 以上の実行 (デバッグまたはリリース) 後にのみマニフェストします。
  2. プロファイリング時にマニフェストしません [私は .NET Memory Profiler 4.5 を使用しています]

特徴: 起動時に、プログラムは ~30MB を使用します。時間が経つにつれて、このタスク マネージャーのメモリ使用量は、最初は 50 ~ 150 MB の間でわずかに低下し始め、最終的には悪化し、200 MB ~ 1 GB 以上の間で振動します。これが発生すると、1 ~ 2 秒の間に数回発生し、次の 10 ~ 20 秒ほどで 150MB 程度に落ち着きます。

私は、メモリ プロファイリングを使用して、この動作を実際にキャッチしようとしています。これまでのところ、私は成功していません。プロファイラーが監視していないときのように、アプリをメモリ使用量でポゴまたは振動させることはできません。ただし、ガベージ コレクターのステージ 1 および 2 の実行時に、メモリ使用量に方形波のようなパターンがあることに気付きました。 800MB+ (200MB から 1GB+) ではなく、10MB 幅。現在、Google Images によると、適切に機能するアプリのガベージ コレクションは、正方形というよりはノコギリ波のように見えます。

率直に言って、私のアプリが 1 秒以内に 200MB から 1GB+ のメモリ使用量を使用し、CPU を 100% に急上昇させない方法は見当たりません。

ガベージ コレクションとイベント処理の間で発生する可能性があるいくつかの問題について読んだことがありますが、調査できるパスがいくつかあり、どのパスに時間を費やすかを絞り込もうとしています。私はまだ .NET がかなり遅く、C を実行している組み込みデバイスに対して持っている「直感」を開発していません。これは、最初に調査する必要があるものをフィルター処理するのに役立ちます。イベント ハンドラーが [膨大な量のデータ] への参照を失ったり、再び取得したりしているような気がする場合はどうでしょう (これがどのように起こるかはわかりません)。ガベージ コレクタが実行され、メモリ使用量が 200MB に戻ります。

このアプリの以前のバージョンには、これらの問題はありませんでした。それ以来、私が行った2つの変更は次のとおりです

  1. 独自のデータ マネージャー (ハードコードされた SQL ステートメントを実行するために使用した ADORecordSetHelper オブジェクトを持っていた) の代わりに LINQ2SQL を利用する
  2. TCP XML メッセージを受信者に送信するために使用するソフトウェアの一部を変更します。#2で行っていることは単純であるため、それが問題の原因になる可能性がありますが、このメモリ使用量の動作は別のことを考えさせます.

この時点での私の主な質問は

  • 作成したメソッドから戻る前に、LINQ2SQL DataContext で dispose を呼び出す必要がありますか?
  • 代わりにそれらを無効にする必要がありますか?
  • DataContext の作成後にメソッドのどこかで例外が発生した場合、DataContext が無期限にメモリに保持される可能性がありますか?
  • LINQ クエリの結果を値型 (つまり、var ではなく int) に格納する場合、その結果は遅延ロードされますか、それとも変数が使用されると遅延ロードされますか?
  • イベント駆動型フレームワークが仮想的に参照を失ったり取り戻したりする可能性はどのくらいありますか?

編集: イベントには、ここで説明したようなインスタンス ベースのサブスクリプションがあり、アプリの存続期間中、サブスクライブが解除されることはありません。

edit2: 最終的にプロファイラーでそれをキャッチすることができました。200MB の system.string が何らかの形で作成されているようです。GC の動作を除外してくれてありがとう。

4

1 に答える 1

4

ほとんどの場合、メモリ リークはオブジェクト間の奇妙な参照によって引き起こされます (イベントとデリゲートもここに含まれます)。

あなたが試すことができると思うのは次のとおりです。

  1. アプリケーションを実行し、問題を再現します。メモリのプライベート ワーキング セットが非常に高い値に達したら、タスク マネージャでプロセスを右クリックし、[ダンプ ファイルの作成] を選択します。これは、アプリケーションをライブでプロファイリングするよりもはるかに邪魔になりません。
  2. WinDBG をダウンロードして実行します。
  3. [ファイル] メニューに移動し、[ダンプ ファイルを開く] を選択してメモリ ダンプを開きます (メニュー オプションの名前を正確に思い出せませんが、簡単に見つけられるはずです)。
  4. 次のコマンドを実行します。

    .symfix

    .loadby sos clr

    !dumpheap -type [YourAssemblyNameSpacePrefix] -stat

  5. 最後のコマンドは、CLR 型ではなく、自分の型だけであるメモリ内のすべてのインスタンスを提供します。インスタンスの数が非常に多いタイプを見て、何か問題があるかどうかを確認してください。

  6. 同じタイプのオブジェクトが非常に多い場合は、次のコマンドを実行すると、すべてのインスタンスのアドレスが表示されます。

    !dumheap -type [TheFullObjectTypeName]

    1 つのインスタンス アドレスを選択する必要があります。次のコマンドを実行して、そのインスタンスへの参照を確認します。

    !gcroot [インスタンスアドレス]

異なるインスタンスに対して手順 6 を数回繰り返して、リークが同じ場所から発生していることを確認したり、これらのインスタンスが収集されない原因を特定したりできるようにします (まだ他のオブジェクトによって参照されています)。

独自の型に異常が見られない場合は、手順 4 の !dumpheap コマンドを !dumpheap -stat に変更します。この方法では、型でフィルター処理するのではなく、CLR の型とサード パーティのライブラリの型も表示されます。

これは少し複雑ですが、メモリ リークを見つける方法を理解するのに役立つ方法を提供できれば幸いです。

于 2013-03-14T11:08:16.177 に答える