すべてが間違っているときにメモリリークを追跡するための戦略

StackOverflow https://stackoverflow.com/questions/600757

  •  03-07-2019
  •  | 
  •  

質問

悲しいかな、私のプログラムにはどこかでメモリリークがありますが、それが何であるかを知っていれば気が滅入るでしょう。

その仕事は、約2MBのファイルを読み込み、解析と文字列の置換を行ってから、それらをさまざまな形式で出力することです。当然、これは多くの文字列を意味するため、メモリトレースを行うと、文字列がたくさんあることがわかります。プログラムの構造は、メモリ内の各ファイルを表すオブジェクトに作用する一連のクラス(私はばかであるため、それぞれ独自のスレッド内にあります)です。 (各オブジェクトには、両端でロックを使用する入力キューがあります。これは、この単純な処理を並行して実行できることを意味しますが、メモリに複数の2MBオブジェクトがあることも意味します。) 。

処理クラスは、処理を完了するとイベントを発生させ、すべての文字列を保持するラージオブジェクトへの参照を渡して、次の処理オブジェクトのキューに追加します。イベントをキューに追加する関数呼び出しで置き換えても、リークは停止しません。出力形式の1つでは、アンマネージオブジェクトを使用する必要があります。クラスにDispose()を実装しても、リークは停止しません。スキーマオブジェクトへのすべての参照をインデックス名に置き換えました。サイコロなし。何が原因なのか、どこを見るべきかわからない。メモリトレースは役に立たない。何故なら、私が見るのは作成される文字列の束であり、メモリ内のどこに参照が貼り付いているかがわからないからだ。

この時点で私たちはほとんどgiveめてロールバックしますが、私はこれをどのように台無しにしたかを正確に知る必要があります。 Stack Overflowはコードを正確にコーミングできないことは知っていますが、このリークを追跡するためにどのような戦略を提案できますか私はおそらく自分の時間にこれを行うつもりなので、どのアプローチも実行可能です。

役に立ちましたか?

解決

私が試みる1つのテクニックは、問題を解消することなく、問題を実証するために必要なコードの量を体系的に減らすことです。これは、「分割統治」として非公式に知られています。そして強力なデバッグ手法です。同じ問題を実証する小さなの例を作成すると、理解しやすくなります。おそらくメモリの問題はその時点でより明確になるでしょう。

他のヒント

あなたを助けることができるのは一人だけです。その人の名前は Tess Ferrandez です。 (静寂)

しかし、真剣に。彼女のブログを読んでください(最初の記事はかなり適切です)。彼女がこのようなものをどのようにデバッグするかを見ると、あなたの問題で何が起こっているかを知るための多くの深い洞察が得られます。

Microsoftの CLRプロファイラーが気に入っています。管理ヒープを視覚化し、リークを追跡するための優れたツールを提供します。

メモリリークを追跡するには、 dotTrace プロファイラーを使用します。方法論的な試行錯誤よりもはるかに決定論的であり、結果がはるかに高速になります。

システムが実行するすべてのアクションについて、スナップショットを取得し、関数の反復を数回実行してから、別のスナップショットを取得します。 2つを比較すると、間に作成されたが解放されなかったすべてのオブジェクトが表示されます。その後、作成時点でスタックフレームを確認できるため、どのインスタンスが解放されていないかがわかります。

入手: http://www.red-gate.com/ Products / ants_profiler / index.htm

メモリとパフォーマンスのプロファイリングは素晴らしいです。推測する代わりに適切な数値を実際に見ることができると、最適化が非常に速くなります。メインアプリのメモリフットプリントを削減するために、仕事でかなり使用しました。

  1. のコンストラクタにコードを追加します 管理されていないオブジェクトをログに記録する onstructed、および一意のIDを並べ替えます。 オブジェクトが 再び破壊されます。 少なくともどれが行くのか教えてください 迷った。
  2. すべての場所のコードをGrepします 新しいオブジェクトを作成します。それに従う あなたが持っているかどうかを確認するコードパス 一致するdestroy。
  3. への連鎖ポインタの追加 構築されたオブジェクトなので、 構築されたオブジェクトへのリンク 現在のものの前後。その後、それらを後でスイープできます。
  4. 参照カウンターを追加します。
  5. 「debug malloc」があります;利用可能ですか?

マネージデバッグアドイン SoS (Son of Strike)マネージメモリの「リーク」を追跡するのに非常に威力を発揮します。これは、定義によりgcルートから発見できるためです。

WinDbgまたはVisual Studioで動作します(ただし、多くの点でWinDbgで使用する方が簡単です)

把握するのは簡単ではありません。 チュートリアル

Tess Fernandezのブログも参照することをお勧めします。

実際にメモリリークが発生しているという事実をどうやって知るのですか?

もう1つのこと:処理クラスがイベントを使用していると書きます。イベントハンドラーを登録している場合、イベントを所有するオブジェクトは存続します。つまり、GCはそれを収集できません。オブジェクトをガベージコレクションする場合は、必ずすべてのイベントハンドラーの登録を解除してください。

" leak"の定義方法に注意してください。 "より多くのメモリを使用する"または「メモリが多すぎる」 「メモリリーク」とは異なります。これは、ガベージコレクション環境で特に当てはまります。単に、GCが使用中の余分なメモリを収集する必要がないということかもしれません。また、仮想メモリの使用と物理メモリの使用の違いにも注意してください。

最後に、すべての"メモリリーク" 「メモリ」が原因です。問題の種類。 IISを頻繁に再起動させていた緊急のメモリリークを修正するように言われました(尋ねられませんでした)。実際、プロファイリングを行ったところ、StringBuilderクラスを通じて多くの文字列を使用していたことがわかりました。 StringBuildersのオブジェクトプール(MSDNの記事から)を実装しましたが、メモリ使用量が大幅に低下しました。

IISは同じ頻度で再起動します。これは、メモリリークがなかったためです。代わりに、スレッドセーフであると主張するがそうではないアンマネージコードがありました。 Webサービス(複数のスレッド)で使用すると、Cランタイムライブラリヒープ全体に書き込みが行われます。管理されていない例外を探している人は誰もいなかったため、自動QAのAQtimeでプロファイリングを行うまで、誰もこれを見ませんでした。イベントウィンドウがあり、Cランタイムライブラリからの苦痛の叫び声が表示されました。

アンマネージコードの呼び出しと「メモリリーク」の周りにロックを配置しました。去った。

アンマネージオブジェクトが本当にリークの原因である場合は、アンマネージメモリと Finalize / Dispose /どこでもアンマネージメモリの割り当てを解除します。これにより、GCは状況をより適切に処理できるようになります。そうでない場合は、収集をスケジュールする必要があることに気付かない可能性があります。

イベントの使用について言及しました。オブジェクトを使い終わったら、それらのイベントからハンドラーを削除しますか? 「緩い」イベントハンドラは、完了時に削除せずに多数のハンドラを追加すると、メモリリークの問題を引き起こすことがわかりました。

.Netに最適なメモリプロファイリングツールは次のとおりです。

http://memprofiler.com

また、私がここにいる間、.Netの最高のパフォーマンスプロファイラーは次のとおりです。

http://www.yourkit.com/dotnet/download/index.jsp

これらはまた、費用対効果が高く、オーバーヘッドが低く、使いやすいです。 .Net開発を真剣に考えている人は、これらの両方を個人的な投資であり、すぐに購入する必要があります。両方とも無料試用版があります。

私は、C#で記述された70万行を超えるコードでリアルタイムゲームエンジンを使用しており、これらのツールを使用して数百時間を費やしました。 2002年以降、Sci Tech製品とYourKitを使用しています。過去3年間。私は他のかなりの数を試しましたが、私は常にこれらに戻りました。

私見、両方とも絶対に素晴らしいです。

チャーリー・マーティンと同様に、次のようなことができます:

static unigned __int64 _foo_id = 0;
foo::foo()
{
    ++_foo_id;
    if (_foo_id == MAGIC_BAD_ALLOC_ID)
        DebugBreak();
    std::werr << L"foo::foo @ " << _foo_id << std::endl;
}
foo::~foo()
{
    --_foo_id;
    std::werr << L"foo::~foo @ " << _foo_id << std::endl;
}

同じ割り当てIDで1回または2回でも再作成できる場合は、その時点で何が起こっているのかを確認できます(明らかに、必要に応じてTLS /スレッディングも処理する必要がありますが、わかりやすくするために)。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top