.NET でメモリ リークを見つけるにはどのような戦略とツールが役立ちますか?
-
02-07-2019 - |
質問
私は 10 年間 C++ を書きました。メモリの問題が発生しましたが、適切な量の努力で解決できました。
ここ数年、私は C# を書いてきました。私はまだ多くの記憶障害を抱えていることに気づきました。これらは非決定性であるため、診断と修正が困難であり、C# の哲学では、確実に行う場合にはそのようなことを心配する必要はないということです。
私が感じた特に問題の 1 つは、コード内のすべてを明示的に破棄してクリーンアップする必要があることです。そうしないと、メモリ プロファイラはあまり役に立ちません。表示しようとしているすべてのデータからリークを見つけることができないほど多くのゴミが漂っているからです。私の考えが間違っているのか、それとも私が持っているツールが最適ではないのか、気になります。
.NET でのメモリ リークに対処するには、どのような戦略やツールが役立ちますか?
解決
サイテックのものを使っています メモリプロファイラー メモリリークが疑われる場合。
これまでのところ、非常に信頼性が高く強力であることがわかりました。少なくとも一度は私のベーコンを救ってくれました。
GC は .NET IMO で非常にうまく機能しますが、他の言語やプラットフォームと同様に、不適切なコードを作成すると、悪いことが起こります。
他のヒント
捨て忘れの問題だけ試してみてください このブログ投稿で説明されている解決策. 。本質は次のとおりです。
public void Dispose ()
{
// Dispose logic here ...
// It's a bad error if someone forgets to call Dispose,
// so in Debug builds, we put a finalizer in to detect
// the error. If Dispose is called, we suppress the
// finalizer.
#if DEBUG
GC.SuppressFinalize(this);
#endif
}
#if DEBUG
~TimedLock()
{
// If this finalizer runs, someone somewhere failed to
// call Dispose, which means we've failed to leave
// a monitor!
System.Diagnostics.Debug.Fail("Undisposed lock");
}
#endif
私たちが使用したのは Ants プロファイラー プロ 私たちのプロジェクトの Red Gate ソフトウェアによって作成されました。すべての .NET 言語ベースのアプリケーションで非常にうまく機能します。
.NET ガベージ コレクターは、メモリ内オブジェクトのクリーンアップにおいて (当然のことですが) 非常に「安全」であることがわかりました。私たちがいるという理由だけで、オブジェクトを周囲に保持することになります かもしれない 将来的には使うことになるでしょう。これは、メモリ内でインフレートするオブジェクトの数についてより注意する必要があることを意味します。最終的に、メモリ オーバーヘッドを削減し、パフォーマンスを向上させるために、すべてのデータ オブジェクトを (フィールドがリクエストされる直前に) 「インフレート オンデマンド」に変換しました。
編集:「オンデマンドの膨張」とはどういう意味かについてのさらなる説明を示します。データベースのオブジェクトモデルでは、親オブジェクトのプロパティを使用して、子オブジェクトを公開します。たとえば、他の「詳細」または「検索」レコードを 1 対 1 で参照するレコードがある場合、次のように構造化します。
class ParentObject
Private mRelatedObject as New CRelatedObject
public Readonly property RelatedObject() as CRelatedObject
get
mRelatedObject.getWithID(RelatedObjectID)
return mRelatedObject
end get
end property
End class
メモリ内に大量のレコードがある場合、上記のシステムでは実際のメモリとパフォーマンスの問題が発生することがわかりました。そこで、オブジェクトが要求された場合にのみインフレートされ、必要な場合にのみデータベース呼び出しが行われるシステムに切り替えました。
class ParentObject
Private mRelatedObject as CRelatedObject
Public ReadOnly Property RelatedObject() as CRelatedObject
Get
If mRelatedObject is Nothing
mRelatedObject = New CRelatedObject
End If
If mRelatedObject.isEmptyObject
mRelatedObject.getWithID(RelatedObjectID)
End If
return mRelatedObject
end get
end Property
end class
これは、オブジェクトが必要になるまで (Get メソッドがアクセスされるまで) メモリ外に保持されるため、はるかに効率的であることが判明しました。これにより、データベースのヒットを制限することでパフォーマンスが大幅に向上し、メモリ領域が大幅に増加しました。
アプリケーションが簡単でない限り、マネージド コードを作成するときもメモリについて心配する必要があります。私は次の 2 つのことを提案します。まず読んでください C# 経由の CLR .NET でのメモリ管理を理解するのに役立つからです。次に、次のようなツールの使い方を学びます。 CLRプロファイラー (マイクロソフト)。これにより、メモリ リークの原因を知ることができます (例:ラージ オブジェクト ヒープの断片化を確認できます)
アンマネージ コードを使用していますか?Microsoft によれば、アンマネージ コードを使用していない場合、従来の意味でのメモリ リークは発生しません。
ただし、アプリケーションが使用するメモリは解放されない場合があるため、アプリケーションのメモリ割り当てはアプリケーションの存続期間全体を通じて増加する可能性があります。
から Microsoft.com で共通言語ランタイムのメモリ リークを特定する方法
アプリケーションの一部として無管理コードを使用すると、.NETフレームワークアプリケーションでメモリリークが発生する可能性があります。この管理されていないコードはメモリを漏らすことができ、.NETフレームワークランタイムはその問題に対処できません。
さらに、プロジェクトにはメモリリークがあるように見える場合があります。この条件は、多くの大きなオブジェクト(Datatableオブジェクトなど)が宣言され、コレクション(データセットなど)に追加された場合に発生する可能性があります。これらのオブジェクトが所有するリソースは決してリリースされない可能性があり、リソースはプログラム全体の実行のために生き残っています。これはリークのように見えますが、実際には、プログラムでメモリが割り当てられている方法の単なる症状です。
このタイプの問題に対処するには、次のように実装できます。 I使い捨て. 。メモリ管理に対処するための戦略をいくつか確認したい場合は、次のように検索することをお勧めします。 IDisposable、XNA、メモリ管理 ゲーム開発者は、より予測可能なガベージ コレクションを必要とするため、GC にその動作を強制する必要があるからです。
よくある間違いの 1 つは、オブジェクトをサブスクライブするイベント ハンドラーを削除しないことです。イベント ハンドラーのサブスクリプションにより、オブジェクトのリサイクルが防止されます。また、こちらもご覧ください。 を使用して このステートメントを使用すると、リソースの有効期間に対して限定されたスコープを作成できます。
このブログ Windbg やその他のツールを使用して、あらゆる種類のメモリ リークを追跡するための非常に素晴らしいチュートリアルがいくつかあります。スキルを向上させるための優れた読書。
Windows サービスでメモリ リークが発生したので修正しました。
まず、試してみました メモリプロファイラー. 。本当に使いにくく、まったくユーザーフレンドリーではないことがわかりました。
それから、私が使用したのは、 ジャストトレース これは使いやすく、正しく破棄されなかったオブジェクトに関するより詳細な情報を提供します。
これにより、メモリリークを非常に簡単に解決できました。
観察しているリークがキャッシュの暴走によるものである場合、これは次のようなシナリオです。 かもしれない WeakReference の使用を検討したいと考えています。これは、必要なときにメモリを確実に解放するのに役立ちます。
ただし、私の意見では、オーダーメイドのソリューションを検討した方がよいでしょう。オブジェクトをどれくらいの期間保持する必要があるのかを実際に知るのはあなただけです。そのため、状況に応じて適切なハウスキーピング コードを設計することが、通常は最善のアプローチです。
大きな銃 - Windows 用のデバッグ ツール
これは素晴らしいツールのコレクションです。これを使用すると、マネージド ヒープとアンマネージド ヒープの両方を分析でき、オフラインで実行できます。これは、メモリの過剰使用によりリサイクルを続ける ASP.NET アプリケーションの 1 つをデバッグするのに非常に便利でした。実稼働サーバーで実行されている生きているプロセスの完全なメモリ ダンプを作成するだけで済み、すべての分析は WinDbg でオフラインで行われました。(一部の開発者がメモリ内のセッション ストレージを過剰に使用していたことが判明しました。)
「壊れたらそれは…」 ブログには、このテーマに関する非常に役立つ記事があります。
心に留めておくべき最善のことは、オブジェクトへの参照を追跡することです。もう気にしなくなったオブジェクトへの参照がハングアップしてしまうことは非常に簡単です。もう使わないものがある場合は、処分しましょう。
有効期限がスライドするキャッシュ プロバイダーの使用に慣れてください。これにより、必要な時間枠内に何かが参照されなかった場合は逆参照されてクリーンアップされます。ただし、頻繁にアクセスされている場合は、メモリ内にあると表示されます。
最良のツールの 1 つは、 Windows 用のデバッグ ツール, を使用してプロセスのメモリ ダンプを取得します。 アドプラス, 、次に使用します ウィンドバック そしてその sos プロセス メモリ、スレッド、呼び出しスタックを分析するプラグイン。
この方法はサーバーの問題の特定にも使用できます。ツールをインストールした後、ディレクトリを共有し、(ネット使用) を使用してサーバーから共有に接続し、プロセスのクラッシュまたはハング ダンプを取得します。
次に、オフラインで分析します。
マネージド アプリケーションを修正した後も、次の変更後にアプリケーションに同じメモリ リークが発生しないことを確認する方法など、同じことが起こりました。そのため、オブジェクト リリース検証フレームワークのようなものを書きました。ぜひご覧ください。 NuGet パッケージ オブジェクトリリース検証. 。ここでサンプルを見つけることができます https://github.com/outcoldman/OutcoldSolutions-ObjectReleaseVerification-Sample, 、およびこのサンプルに関する情報 http://outcoldman.ru/en/blog/show/322
私は好きです ドットメモリ ジェットブレインズより
Visual Studio 2015 以降は、そのまま使用することを検討してください。 メモリ使用量診断ツール メモリ使用量データを収集して分析します。
メモリ使用量ツールを使用すると、マネージド メモリ ヒープとネイティブ メモリ ヒープの 1 つ以上のスナップショットを取得して、オブジェクト タイプのメモリ使用量の影響を理解することができます。