この記事では、ガベージ コレクションとメモリ使用量に関連する問題について説明します。 マネージド ヒープに関連する問題に対処し、アプリケーションに対するガベージ コレクションの影響を最小限に抑える方法について説明します。 各問題には、問題の調査に使用できる手順へのリンクがあります。
パフォーマンス分析ツール
次のセクションでは、メモリ使用量とガベージ コレクションの問題を調査するために使用できるツールについて説明します。 この記事で後述 する手順 は、これらのツールを参照します。
メモリ パフォーマンス カウンター
パフォーマンス カウンターを使用して、パフォーマンス データを収集できます。 手順については、「 ランタイム プロファイル」を参照してください。 パフォーマンス カウンターの .NET CLR メモリ カテゴリは、「 .NET のパフォーマンス カウンター」で説明されているように、ガベージ コレクターに関する情報を提供します。
SOS を使用したデバッグ
Windows デバッガー (WinDbg) を使用して、マネージド ヒープ上のオブジェクトを検査できます。
WinDbg をインストールするには、[Windows 用デバッグ ツールのダウンロード] ページから Windows 用デバッグ ツールをインストール します。
ガベージ コレクション ETW イベント
Windows のイベント トレース (ETW) は、.NET によって提供されるプロファイリングとデバッグのサポートを補完するトレース システムです。 .NET Framework 4 以降では、 ガベージ コレクション ETW イベント は、統計的な観点からマネージド ヒープを分析するのに役立つ情報をキャプチャします。 たとえば、ガベージ コレクションが発生しようとしたときに発生する GCStart_V1
イベントは、次の情報を提供します。
- 収集されるオブジェクトの生成。
- ガベージ コレクションをトリガーした内容。
- ガベージ コレクションの種類 (同時実行または同時実行ではない)。
ETW イベント ログは効率的であり、ガベージ コレクションに関連するパフォーマンスの問題はマスクされません。 プロセスは、ETW イベントと組み合わせて独自のイベントを提供できます。 ログに記録されると、アプリケーションのイベントとガベージ コレクション イベントの両方を関連付けて、ヒープの問題が発生する方法とタイミングを判断できます。 たとえば、サーバー アプリケーションは、クライアント要求の開始時と終了時にイベントを提供できます。
プロファイル API
共通言語ランタイム (CLR) プロファイリング インターフェイスは、ガベージ コレクション中に影響を受けたオブジェクトに関する詳細情報を提供します。 プロファイラーは、ガベージ コレクションの開始時と終了時に通知を受け取ることができます。 各世代のオブジェクトの識別など、マネージド ヒープ上のオブジェクトに関するレポートを提供できます。 詳細については、「 プロファイリングの概要」を参照してください。
プロファイラーは、包括的な情報を提供できます。 ただし、複雑なプロファイラーは、アプリケーションの動作を変更する可能性があります。
アプリケーション ドメイン リソースの監視
.NET Framework 4 以降では、アプリケーション ドメイン リソース監視 (ARM) を使用すると、ホストはアプリケーション ドメインごとに CPU とメモリの使用状況を監視できます。 詳細については、「 アプリケーション ドメイン リソースの監視」を参照してください。
パフォーマンスに関する問題のトラブルシューティング
最初の手順では、 問題が実際にガベージ コレクションであるかどうかを判断します。 それが正しいと判断した場合は、次の一覧から選択して問題のトラブルシューティングを行います。
- メモリ不足例外がスローされる
- プロセスで使用されるメモリが多すぎる
- ガベージ コレクターがオブジェクトを十分に高速に再利用できない
- マネージド ヒープが断片化しすぎる
- ガベージ コレクションの一時停止が長すぎます
- ジェネレーション 0 が大きすぎます
- ガベージ コレクション中の CPU 使用率が高すぎます
問題: メモリ不足例外がスローされる
マネージド OutOfMemoryException がスローされる正当なケースは 2 つあります。
仮想メモリが不足しています。
ガベージ コレクターは、事前に決定されたサイズのセグメントでシステムからメモリを割り当てます。 割り当てに追加のセグメントが必要であっても、プロセスの仮想メモリ領域に連続する空きブロックが残っていない場合、マネージド ヒープの割り当ては失敗します。
割り当てるのに十分な物理メモリがありません。
パフォーマンス チェック |
---|
メモリ不足例外が管理されているかどうかを判断します。 予約できる仮想メモリの量を決定します。 十分な物理メモリがあるかどうかを判断します。 |
例外が正当でないと判断した場合は、次の情報を Microsoft カスタマー サービスおよびサポートにお問い合わせください。
- メモリ不足のマネージド例外を含むスタック。
- 完全メモリ ダンプ。
- 仮想または物理メモリが問題ではないことを示すデータを含め、それがメモリ不足の正当な例外ではないことを証明するデータ。
問題: プロセスでメモリが多すぎる
一般的な前提は、Windows タスク マネージャーの [ パフォーマンス ] タブにメモリ使用量が表示され、使用されているメモリの量が多すぎることを示す可能性があるということです。 ただし、この表示はワーキング セットに関連します。仮想メモリ使用量に関する情報は提供されません。
問題がマネージド ヒープによって発生していると判断した場合は、時間の経過と同時にマネージド ヒープを測定して、パターンを特定する必要があります。
問題がマネージド ヒープによって引き起こされていないと判断した場合は、ネイティブ デバッグを使用する必要があります。
パフォーマンス チェック |
---|
予約できる仮想メモリの量を決定します。 マネージド ヒープがコミットしているメモリの量を確認します。 マネージド ヒープが予約するメモリの量を決定します。 第 2 世代の大きなオブジェクトを特定します。 オブジェクトへの参照を決定します。 |
問題: ガベージ コレクターがオブジェクトを十分に高速に再利用できない
オブジェクトがガベージ コレクションに対して期待どおりに再利用されていないかのように見える場合は、それらのオブジェクトへの強力な参照があるかどうかを判断する必要があります。
また、配信不能オブジェクトを含むジェネレーションのガベージ コレクションが存在しない場合にも、この問題が発生する可能性があります。これは、配信不能オブジェクトのファイナライザーが実行されていないことを示します。 たとえば、シングル スレッド アパートメント (STA) アプリケーションを実行していて、ファイナライザー キューにサービスを提供するスレッドが呼び出せない場合に、これが可能です。
パフォーマンス チェック |
---|
オブジェクトへの参照を確認します。 ファイナライザーが実行されたかどうかを確認します。 最終処理を待機しているオブジェクトがあるかどうかを判断します。 |
問題: マネージド ヒープが断片化しすぎている
断片化レベルは、世代に割り当てられたメモリの合計に対する空き領域の比率として計算されます。 第 2 世代の場合、許容できる断片化レベルは 20%以下です。 ジェネレーション 2 は非常に大きくなるため、断片化の比率は絶対値よりも重要です。
新しいオブジェクトが割り当てられる世代であるため、ジェネレーション 0 に多くの空き領域を持つことは問題ではありません。
断片化は、圧縮されていないため、常にラージ オブジェクト ヒープで発生します。 隣接するフリー オブジェクトは、大きなオブジェクトの割り当て要求を満たすために、1 つのスペースに自然に折りたたまれます。
第 1 世代と第 2 世代では断片化が問題になる可能性があります。 ガベージ コレクションの後にこれらの世代に大量の空き領域がある場合は、アプリケーションのオブジェクトの使用に変更が必要な場合があり、長期的なオブジェクトの有効期間の再評価を検討する必要があります。
オブジェクトを過度にピン留めすると、断片化が増加する可能性があります。 断片化が高い場合は、ピン留めされたオブジェクトが多すぎる可能性があります。
仮想メモリの断片化によってガベージ コレクターがセグメントを追加できない場合、原因は次のいずれかである可能性があります。
多数の小さなアセンブリの頻繁な読み込みとアンロード。
アンマネージ コードとの相互運用時に COM オブジェクトへの参照が多すぎます。
大規模な一時的なオブジェクトの作成。これにより、大きなオブジェクト ヒープが頻繁に割り当てられ、ヒープ セグメントが解放されます。
CLR をホストする場合、アプリケーションはガベージ コレクターにそのセグメントを保持することを要求できます。 これにより、セグメント割り当ての頻度が減少します。 これを行うには、STARTUP_FLAGS 列挙で STARTUP_HOARD_GC_VM フラグを使用します。
パフォーマンス チェック |
---|
マネージド ヒープ内の空き領域の量を決定します。 ピン留めされたオブジェクトの数を決定します。 |
断片化の正当な原因がないと思われる場合は、Microsoft カスタマー サービスとサポートにお問い合わせください。
問題: ガベージ コレクションの一時停止が長すぎます
ガベージ コレクションはソフト リアルタイムで動作するため、アプリケーションは一時停止を許容できる必要があります。 ソフト リアルタイムの基準は、95% の操作を時間に応じて完了する必要があるということです。
同時実行ガベージ コレクションでは、マネージド スレッドはコレクション中に実行できます。つまり、一時停止は非常に最小限です。
エフェメラル ガベージ コレクション (ジェネレーション 0 とジェネレーション 1) は数ミリ秒しか持続しないため、通常、一時停止を減らすことは不可能です。 ただし、アプリケーションによる割り当て要求のパターンを変更することで、第 2 世代コレクションの一時停止を減らすことができます。
もう 1 つのより正確な方法は、 ガベージ コレクション ETW イベントを使用することです。 一連のイベントにタイム スタンプの違いを追加することで、コレクションのタイミングを確認できます。 コレクション シーケンス全体には、実行エンジンの中断、ガベージ コレクション自体、実行エンジンの再開が含まれます。
ガベージ コレクション通知を使用して、サーバーがジェネレーション 2 のコレクションを使用しようとしているかどうか、および別のサーバーに要求を再ルーティングすると、一時停止に関する問題が緩和される可能性があるかどうかを判断できます。
パフォーマンス チェック |
---|
ガベージ コレクション内の時間の長さを決定します。 ガベージ コレクションの原因を特定します。 |
問題: ジェネレーション 0 が大きすぎる
ジェネレーション 0 では、特にワークステーションのガベージ コレクションではなくサーバー ガベージ コレクションを使用する場合に、64 ビット システム上のオブジェクトの数が多くなる可能性があります。 これは、これらの環境ではジェネレーション 0 のガベージ コレクションをトリガーするしきい値が高く、ジェネレーション 0 のコレクションがはるかに大きくなる可能性があるためです。 ガベージ コレクションがトリガーされる前に、アプリケーションがより多くのメモリを割り当てると、パフォーマンスが向上します。
問題: ガベージ コレクション中の CPU 使用率が高すぎます
ガベージ コレクション中は CPU 使用率が高くなります。 ガベージ コレクションに大量の処理時間が費やされた場合、コレクションの数が多すぎるか、コレクションが長すぎます。 マネージド ヒープ上のオブジェクトの割り当て率が高くなると、ガベージ コレクションの実行頻度が高くなります。 割り当て率を下げると、ガベージ コレクションの頻度が減少します。
Allocated Bytes/second
パフォーマンス カウンターを使用して、割り当て率を監視できます。 詳細については、「 .NET のパフォーマンス カウンター」を参照してください。
コレクションの期間は、主に割り当て後に存続するオブジェクトの数の要因です。 多数のオブジェクトが収集されたままの場合、ガベージ コレクターは大量のメモリを通過する必要があります。 生存者を最適化する作業には時間がかかります。 コレクション中に処理されたオブジェクトの数を確認するには、指定した世代のガベージ コレクションの最後にデバッガーにブレークポイントを設定します。
パフォーマンス チェック |
---|
CPU 使用率の高さがガベージ コレクションによって発生しているかどうかを判断します。 ガベージ コレクションの最後にブレークポイントを設定します。 |
トラブルシューティングのガイドライン
このセクションでは、調査を開始するときに考慮する必要があるガイドラインについて説明します。
ワークステーションまたはサーバーのガベージ コレクション
適切な種類のガベージ コレクションを使用しているかどうかを確認します。 アプリケーションで複数のスレッドとオブジェクト インスタンスを使用する場合は、ワークステーションのガベージ コレクションではなくサーバー ガベージ コレクションを使用します。 サーバー ガベージ コレクションは複数のスレッドで動作しますが、ワークステーションのガベージ コレクションでは、アプリケーションの複数のインスタンスが独自のガベージ コレクション スレッドを実行し、CPU 時間を競う必要があります。
負荷が低く、サービスなどのバックグラウンドで頻繁にタスクを実行するアプリケーションでは、同時実行ガベージ コレクションを無効にしたワークステーション ガベージ コレクションを使用できます。
マネージド ヒープ サイズを測定するタイミング
プロファイラーを使用している場合を除き、パフォーマンスの問題を効果的に診断するには、一貫した測定パターンを確立する必要があります。 スケジュールを確立するには、次の点を考慮してください。
- ジェネレーション 2 のガベージ コレクションの後で測定する場合、マネージド ヒープ全体にガベージ (デッド オブジェクト) は含められません。
- ジェネレーション 0 のガベージ コレクションの直後に測定した場合、ジェネレーション 1 とジェネレーション 2 のオブジェクトはまだ収集されません。
- ガベージ コレクションの直前に測定する場合は、ガベージ コレクションが開始される前に、できるだけ多くの割り当てを測定します。
- ガベージ コレクターのデータ構造がトラバーサルに対して有効な状態ではなく、完全な結果を得られない可能性があるため、ガベージ コレクション中の測定には問題があります。 これは仕様です。
- 同時実行ガベージ コレクションと共にワークステーション ガベージ コレクションを使用している場合、回収されたオブジェクトは圧縮されないため、ヒープ サイズは同じでも大きくすることもできます (断片化が大きくなる可能性があります)。
- 物理メモリの負荷が高すぎると、ジェネレーション 2 での同時実行ガベージ コレクションが遅延します。
次の手順では、マネージド ヒープを測定できるようにブレークポイントを設定する方法について説明します。
ガベージ コレクションの最後にブレークポイントを設定するには
SOS デバッガー拡張機能が読み込まれた WinDbg で、次のコマンドを入力します。
bp mscorwks!WKS::GCHeap::RestartEE "j (dwo(mscorwks!WKS::GCHeap::GcCondemnedGeneration)==2) 'kb';'g'"
GcCondemnedGeneration
を目的の世代に設定します。 このコマンドにはプライベート シンボルが必要です。ジェネレーション 2 のオブジェクトがガベージ コレクション用に再利用された後に
RestartEE
が実行された場合、このコマンドは強制的に中断します。サーバー ガベージ コレクションでは、
RestartEE
呼び出すスレッドは 1 つだけであるため、ブレークポイントはジェネレーション 2 のガベージ コレクション中に 1 回だけ発生します。
パフォーマンス チェックの手順
このセクションでは、パフォーマンスの問題の原因を特定するための次の手順について説明します。
- 問題がガベージ コレクションによって発生しているかどうかを確認します。
- メモリ不足例外が管理されているかどうかを判断します。
- 予約できる仮想メモリの量を決定します。
- 十分な物理メモリがあるかどうかを判断します。
- マネージド ヒープがコミットしているメモリの量を確認します。
- マネージド ヒープが予約するメモリの量を決定します。
- 第 2 世代の大きなオブジェクトを特定します。
- オブジェクトへの参照を決定します。
- ファイナライザーが実行されたかどうかを確認します。
- 最終処理を待機しているオブジェクトがあるかどうかを判断します。
- マネージド ヒープ内の空き領域の量を決定します。
- ピン留めされたオブジェクトの数を決定します。
- ガベージ コレクション内の時間の長さを決定します。
- ガベージ コレクションをトリガーした原因を特定します。
- CPU 使用率の高さがガベージ コレクションによって発生しているかどうかを判断します。
問題がガベージ コレクションによって発生しているかどうかを判断するには
次の 2 つのメモリ パフォーマンス カウンターを確認します。
GC で時刻を% します。 最後のガベージ コレクション サイクル後にガベージ コレクションの実行に費やされた経過時間の割合を表示します。 このカウンターを使用して、ガベージ コレクターがマネージド ヒープ領域を使用できるようにするために時間がかかりすぎるかどうかを判断します。 ガベージ コレクションに費やされる時間が比較的少ない場合は、マネージド ヒープの外部にあるリソースの問題を示している可能性があります。 同時実行ガベージ コレクションまたはバックグラウンド ガベージ コレクションが関係している場合、このカウンターは正確でない可能性があります。
# コミットされた合計バイト数。 ガベージ コレクターによって現在コミットされている仮想メモリの量を表示します。 このカウンターを使用して、ガベージ コレクターによって消費されるメモリが、アプリケーションで使用されるメモリの過剰な部分であるかどうかを判断します。
ほとんどのメモリ パフォーマンス カウンターは、各ガベージ コレクションの終了時に更新されます。 そのため、情報が必要な現在の条件が反映されていない可能性があります。
メモリ不足例外が管理されているかどうかを確認するには
SOS デバッガー拡張機能が読み込まれた WinDbg または Visual Studio デバッガーで、印刷例外 (
pe
) コマンドを入力します。!pe
例外が管理されている場合、次の例に示すように、 OutOfMemoryException は例外の種類として表示されます。
Exception object: 39594518 Exception type: System.OutOfMemoryException Message: <none> InnerException: <none> StackTrace (generated):
出力で例外が指定されていない場合は、メモリ不足例外の元のスレッドを特定する必要があります。 デバッガーで次のコマンドを入力して、すべてのスレッドとその呼び出し履歴を表示します。
~\*kb
例外呼び出しがあるスタックを持つスレッドは、
RaiseTheException
引数によって示されます。 これはマネージド例外オブジェクトです。28adfb44 7923918f 5b61f2b4 00000000 5b61f2b4 mscorwks!RaiseTheException+0xa0
次のコマンドを使用して、入れ子になった例外をダンプできます。
!pe -nested
例外が見つからない場合、メモリ不足例外はアンマネージ コードから発生します。
予約できる仮想メモリの量を確認するには
SOS デバッガー拡張機能が読み込まれた WinDbg で、次のコマンドを入力して最大の空き領域を取得します。
!address -summary
最大の空き領域は、次の出力に示すように表示されます。
Largest free region: Base 54000000 - Size 0003A980
この例では、最大の空き領域のサイズは約 2,4000 KB (16 進数で 3A980) です。 この領域は、ガベージ コレクターがセグメントに必要とする領域よりもはるかに小さくなります。
- または -
vmstat
コマンドを使用します。!vmstat
最大の空き領域は、次の出力に示すように、MAXIMUM 列の最大値です。
TYPE MINIMUM MAXIMUM AVERAGE BLK COUNT TOTAL ~~~~ ~~~~~~~ ~~~~~~~ ~~~~~~~ ~~~~~~~~~~ ~~~~ Free: Small 8K 64K 46K 36 1,671K Medium 80K 864K 349K 3 1,047K Large 1,384K 1,278,848K 151,834K 12 1,822,015K Summary 8K 1,278,848K 35,779K 51 1,824,735K
十分な物理メモリがあるかどうかを判断するには
Windows タスク マネージャーを起動します。
[
Performance
] タブで、コミットされた値を確認します。 (Windows 7 では、System group
のCommit (KB)
を参照してください)。Total
がLimit
に近い場合は、物理メモリが不足しています。
マネージド ヒープがコミットしているメモリの量を確認するには
# Total committed bytes
メモリ パフォーマンス カウンターを使用して、マネージド ヒープがコミットしているバイト数を取得します。 ガベージ コレクターは、必要に応じてセグメントのチャンクをコミットしますが、すべて同時にコミットするわけではありません。注
# Bytes in all Heaps
パフォーマンス カウンターは、マネージド ヒープによる実際のメモリ使用量を表していないため、使用しないでください。 世代のサイズはこの値に含まれており、実際にはしきい値のサイズ、つまり、生成がオブジェクトで満たされている場合にガベージ コレクションを誘発するサイズです。 したがって、この値は通常 0 です。
マネージド ヒープが予約するメモリの量を確認するには
# Total reserved bytes
メモリ パフォーマンス カウンターを使用します。ガベージ コレクターはセグメント内のメモリを予約し、
eeheap
コマンドを使用してセグメントの開始位置を決定できます。Von Bedeutung
ガベージ コレクターが各セグメントに割り当てるメモリの量を決定できますが、セグメント サイズは実装固有であり、定期的な更新を含め、いつでも変更される可能性があります。 アプリでは、セグメント サイズを推測することや、特定のセグメント サイズに依存することを絶対に避けてください。また、セグメントの割り当てに使用可能なメモリの量を構成しようとしてもなりません。
SOS デバッガー拡張機能が読み込まれた WinDbg または Visual Studio デバッガーで、次のコマンドを入力します。
!eeheap -gc
結果は次のようになります。
Number of GC Heaps: 2 ------------------------------ Heap 0 (002db550) generation 0 starts at 0x02abe29c generation 1 starts at 0x02abdd08 generation 2 starts at 0x02ab0038 ephemeral segment allocation context: none segment begin allocated size 02ab0000 02ab0038 02aceff4 0x0001efbc(126908) Large object heap starts at 0x0aab0038 segment begin allocated size 0aab0000 0aab0038 0aab2278 0x00002240(8768) Heap Size 0x211fc(135676) ------------------------------ Heap 1 (002dc958) generation 0 starts at 0x06ab1bd8 generation 1 starts at 0x06ab1bcc generation 2 starts at 0x06ab0038 ephemeral segment allocation context: none segment begin allocated size 06ab0000 06ab0038 06ab3be4 0x00003bac(15276) Large object heap starts at 0x0cab0038 segment begin allocated size 0cab0000 0cab0038 0cab0048 0x00000010(16) Heap Size 0x3bbc(15292) ------------------------------ GC Heap Size 0x24db8(150968)
"segment" で示されるアドレスは、セグメントの開始アドレスです。
第 2 世代のラージ オブジェクトを特定するには
SOS デバッガー拡張機能が読み込まれた WinDbg または Visual Studio デバッガーで、次のコマンドを入力します。
!dumpheap –stat
マネージド ヒープが大きい場合、
dumpheap
が完了するまでに時間がかかる場合があります。最も多くのスペースを使用するオブジェクトが一覧表示されるため、出力の最後の数行から分析を開始できます。 例えば次が挙げられます。
2c6108d4 173712 14591808 DevExpress.XtraGrid.Views.Grid.ViewInfo.GridCellInfo 00155f80 533 15216804 Free 7a747c78 791070 15821400 System.Collections.Specialized.ListDictionary+DictionaryNode 7a747bac 700930 19626040 System.Collections.Specialized.ListDictionary 2c64e36c 78644 20762016 DevExpress.XtraEditors.ViewInfo.TextEditViewInfo 79124228 121143 29064120 System.Object[] 035f0ee4 81626 35588936 Toolkit.TlkOrder 00fcae40 6193 44911636 WaveBasedStrategy.Tick_Snap[] 791242ec 40182 90664128 System.Collections.Hashtable+bucket[] 790fa3e0 3154024 137881448 System.String Total 8454945 objects
リストされている最後のオブジェクトは文字列であり、最も多くの領域を占有します。 アプリケーションを調べて、文字列オブジェクトを最適化する方法を確認できます。 150 ~ 200 バイトの文字列を表示するには、次のように入力します。
!dumpheap -type System.String -min 150 -max 200
結果の例を次に示します。
Address MT Size Gen 1875d2c0 790fa3e0 152 2 System.String HighlightNullStyle_Blotter_PendingOrder-11_Blotter_PendingOrder-11 …
ID に文字列の代わりに整数を使用する方が効率的です。 同じ文字列が何千回も繰り返されている場合は、文字列のインターンを検討してください。 文字列のインターンの詳細については、 String.Intern メソッドのリファレンス トピックを参照してください。
オブジェクトへの参照を決定するには
SOS デバッガー拡張機能が読み込まれた WinDbg で、次のコマンドを入力してオブジェクトへの参照を一覧表示します。
!gcroot
- または -
特定のオブジェクトの参照を確認するには、アドレスを含めます。
!gcroot 1c37b2ac
スタック上で見つかったルートは、誤検知である可能性があります。 詳細については、コマンド
!help gcroot
を使用してください。ebx:Root:19011c5c(System.Windows.Forms.Application+ThreadContext)-> 19010b78(DemoApp.FormDemoApp)-> 19011158(System.Windows.Forms.PropertyStore)-> … [omitted] 1c3745ec(System.Data.DataTable)-> 1c3747a8(System.Data.DataColumnCollection)-> 1c3747f8(System.Collections.Hashtable)-> 1c376590(System.Collections.Hashtable+bucket[])-> 1c376c98(System.Data.DataColumn)-> 1c37b270(System.Data.Common.DoubleStorage)-> 1c37b2ac(System.Double[]) Scan Thread 0 OSTHread 99c Scan Thread 6 OSTHread 484
gcroot
コマンドの完了には長い時間がかかる場合があります。 ガベージ コレクションによって回収されないオブジェクトは、ライブ オブジェクトです。 つまり、ルートが直接または間接的にオブジェクトを保持しているため、gcroot
はオブジェクトにパス情報を返す必要があります。 返されたグラフを調べて、これらのオブジェクトがまだ参照されている理由を確認する必要があります。
ファイナライザーが実行されたかどうかを確認するには
次のコードを含むテスト プログラムを実行します。
GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
テストで問題が解決された場合は、ガベージ コレクターがオブジェクトを再利用していなかったことを意味します。これらのオブジェクトのファイナライザーが中断されていたためです。 GC.WaitForPendingFinalizers メソッドを使用すると、ファイナライザーはタスクを完了し、問題を修正できます。
最終処理を待機しているオブジェクトがあるかどうかを判断するには
SOS デバッガー拡張機能が読み込まれた WinDbg または Visual Studio デバッガーで、次のコマンドを入力します。
!finalizequeue
最終処理の準備ができているオブジェクトの数を確認します。 数値が大きい場合は、これらのファイナライザーがまったく進行できないか、または十分な速さで進行できない理由を調べる必要があります。
スレッドの出力を取得するには、次のコマンドを入力します。
!threads -special
このコマンドは、次のような出力を提供します。
OSID Special thread type 2 cd0 DbgHelper 3 c18 Finalizer 4 df0 GC SuspendEE
ファイナライザー スレッドは、現在実行されているファイナライザー (ある場合) を示します。 ファイナライザー スレッドがファイナライザーを実行していない場合、イベントがその処理を実行するように通知するのを待機しています。 ほとんどの場合、ファイナライザー スレッドはTHREAD_HIGHEST_PRIORITYで実行され、ファイナライザーがある場合は非常に迅速に実行が完了するため、この状態になります。
マネージド ヒープ内の空き領域の量を確認するには
SOS デバッガー拡張機能が読み込まれた WinDbg または Visual Studio デバッガーで、次のコマンドを入力します。
!dumpheap -type Free -stat
このコマンドは、次の例に示すように、マネージド ヒープ上のすべての空きオブジェクトの合計サイズを表示します。
total 230 objects Statistics: MT Count TotalSize Class Name 00152b18 230 40958584 Free Total 230 objects
ジェネレーション 0 の空き領域を確認するには、世代別のメモリ消費情報に対して次のコマンドを入力します。
!eeheap -gc
このコマンドは、次のような出力を表示します。 最後の行には、エフェメラル セグメントが表示されます。
Heap 0 (0015ad08) generation 0 starts at 0x49521f8c generation 1 starts at 0x494d7f64 generation 2 starts at 0x007f0038 ephemeral segment allocation context: none segment begin allocated size 00178250 7a80d84c 7a82f1cc 0x00021980(137600) 00161918 78c50e40 78c7056c 0x0001f72c(128812) 007f0000 007f0038 047eed28 0x03ffecf0(67103984) 3a120000 3a120038 3a3e84f8 0x002c84c0(2917568) 46120000 46120038 49e05d04 0x03ce5ccc(63855820)
ジェネレーション 0 で使用される領域を計算します。
? 49e05d04-0x49521f8c
結果は次のようになります。 ジェネレーション 0 は約 9 MB です。
Evaluate expression: 9321848 = 008e3d78
次のコマンドは、ジェネレーション 0 の範囲内の空き領域をダンプします。
!dumpheap -type Free -stat 0x49521f8c 49e05d04
結果は次のようになります。
------------------------------ Heap 0 total 409 objects ------------------------------ Heap 1 total 0 objects ------------------------------ Heap 2 total 0 objects ------------------------------ Heap 3 total 0 objects ------------------------------ total 409 objects Statistics: MT Count TotalSize Class Name 0015a498 409 7296540 Free Total 409 objects
この出力は、ヒープの第 0 世代部分がオブジェクトに 9 MB の領域を使用しており、7 MB の空き容量があることを示しています。 この分析は、ジェネレーション 0 が断片化にどの程度寄与するかを示しています。 このヒープ使用量は、長期的なオブジェクトによる断片化の原因として、合計量から割引する必要があります。
ピン留めされたオブジェクトの数を確認するには
SOS デバッガー拡張機能が読み込まれた WinDbg または Visual Studio デバッガーで、次のコマンドを入力します。
!gchandles
次の例に示すように、表示される統計情報には、ピン留めされたハンドルの数が含まれます。
GC Handle Statistics: Strong Handles: 29 Pinned Handles: 10
ガベージ コレクション内の時間の長さを確認するには
% Time in GC
メモリ パフォーマンス カウンターを調べます。この値は、サンプル間隔時間を使用して計算されます。 カウンターは各ガベージ コレクションの最後に更新されるため、現在のサンプルは、間隔中にコレクションが発生しなかった場合、前のサンプルと同じ値になります。
収集時間は、サンプル間隔の時間とパーセント値を乗算することによって取得されます。
次のデータは、8 秒間の 2 秒の 4 つのサンプリング間隔を示しています。
Gen0
、Gen1
、およびGen2
の列には、その世代の間隔の終わりまでに完了したガベージ コレクションの合計数が表示されます。Interval Gen0 Gen1 Gen2 % Time in GC 1 9 3 1 10 2 10 3 1 1 3 11 3 1 3 4 11 3 1 3
この情報はガベージ コレクションがいつ発生したかを示しませんが、一定の間隔で発生したガベージ コレクションの数を特定できます。 最悪の場合、第 10 世代 0 のガベージ コレクションは 2 番目の間隔の開始時に終了し、第 11 世代 0 のガベージ コレクションは 3 番目の間隔の最後に終了しました。 10 番目のガベージ コレクションの終了から 11 番目のガベージ コレクションの終了までの時間は約 2 秒で、パフォーマンス カウンターには 3%が表示されるため、第 11 世代 0 のガベージ コレクションの期間は (2 秒 * 3% = 60 ミリ秒) でした。
次の例では、5 つの間隔があります。
Interval Gen0 Gen1 Gen2 % Time in GC 1 9 3 1 3 2 10 3 1 1 3 11 4 1 1 4 11 4 1 1 5 11 4 2 20
第 2 世代 2 のガベージ コレクションは、4 番目の間隔で開始され、5 番目の間隔で終了しました。 最悪のケースを想定すると、最後のガベージ コレクションは、3 番目の間隔の開始時に終了したジェネレーション 0 コレクション用であり、ジェネレーション 2 のガベージ コレクションは 5 番目の間隔の最後に終了しました。 そのため、ジェネレーション 0 ガベージ コレクションの終了からジェネレーション 2 ガベージ コレクションの終了までの時間は 4 秒です。
% Time in GC
カウンターは 20%であるため、ジェネレーション 2 のガベージ コレクションにかかる最大時間は (4 秒 * 20% = 800 ミリ秒) です。または、ガベージ コレクション ETW イベントを使用してガベージ コレクションの長さを判断し、情報を分析してガベージ コレクションの期間を決定することもできます。
たとえば、次のデータは、非同時実行ガベージ コレクション中に発生したイベント シーケンスを示しています。
Timestamp Event name 513052 GCSuspendEEBegin_V1 513078 GCSuspendEEEnd 513090 GCStart_V1 517890 GCEnd_V1 517894 GCHeapStats 517897 GCRestartEEBegin 517918 GCRestartEEEnd
マネージド スレッドの中断に 26us (
GCSuspendEEEnd
–GCSuspendEEBegin_V1
) かかりました。実際のガベージ コレクションには 4.8 ミリ秒 (
GCEnd_V1
–GCStart_V1
) かかりました。マネージド スレッドの再開には 21us (
GCRestartEEEnd
–GCRestartEEBegin
) かかりました。次の出力は、バックグラウンド ガベージ コレクションの例を示し、プロセス、スレッド、およびイベント フィールドを含みます。 (すべてのデータが表示されるわけではありません)。
timestamp(us) event name process thread event field 42504385 GCSuspendEEBegin_V1 Test.exe 4372 1 42504648 GCSuspendEEEnd Test.exe 4372 42504816 GCStart_V1 Test.exe 4372 102019 42504907 GCStart_V1 Test.exe 4372 102020 42514170 GCEnd_V1 Test.exe 4372 42514204 GCHeapStats Test.exe 4372 102020 42832052 GCRestartEEBegin Test.exe 4372 42832136 GCRestartEEEnd Test.exe 4372 63685394 GCSuspendEEBegin_V1 Test.exe 4744 6 63686347 GCSuspendEEEnd Test.exe 4744 63784294 GCRestartEEBegin Test.exe 4744 63784407 GCRestartEEEnd Test.exe 4744 89931423 GCEnd_V1 Test.exe 4372 102019 89931464 GCHeapStats Test.exe 4372
42504816の
GCStart_V1
イベントは、最後のフィールドが1
されているため、これがバックグラウンド ガベージ コレクションであることを示します。 これがガベージ コレクション No. 102019になります。GCStart
イベントは、バックグラウンド ガベージ コレクションを開始する前にエフェメラル ガベージ コレクションが必要であるために発生します。 これがガベージ コレクション番号 102020 になります。42514170では、ガベージ コレクション番号 102020 が終了します。 この時点で、マネージド スレッドが再起動されます。 これは、このバックグラウンド ガベージ コレクションをトリガーしたスレッド 4372 で完了します。
スレッド 4744 では、中断が発生します。 これは、バックグラウンド ガベージ コレクションがマネージド スレッドを中断する必要がある唯一の時間です。 この期間は約 99 ミリ秒 ((63784407-63685394)/1000)。
バックグラウンド ガベージ コレクションの
GCEnd
イベントは、89931423です。 これは、バックグラウンド ガベージ コレクションが約 47 秒続いた ((89931423-42504816)/1000) ことを意味します。マネージド スレッドの実行中に、任意の数のエフェメラル ガベージ コレクションが発生していることがわかります。
ガベージ コレクションをトリガーした原因を特定するには
SOS デバッガー拡張機能が読み込まれた WinDbg または Visual Studio デバッガーで、次のコマンドを入力して、すべてのスレッドとその呼び出し履歴を表示します。
~*kb
このコマンドは、次のような出力を表示します。
0012f3b0 79ff0bf8 mscorwks!WKS::GCHeap::GarbageCollect 0012f454 30002894 mscorwks!GCInterface::CollectGeneration+0xa4 0012f490 79fa22bd fragment_ni!request.Main(System.String[])+0x48
ガベージ コレクションがオペレーティング システムからのメモリ不足の通知によって発生した場合、呼び出し履歴は似ていますが、スレッドがファイナライザー スレッドである点が異なります。 ファイナライザー スレッドは非同期のメモリ不足通知を取得し、ガベージ コレクションを誘発します。
ガベージ コレクションがメモリ割り当てによって発生した場合、スタックは次のように表示されます。
0012f230 7a07c551 mscorwks!WKS::GCHeap::GarbageCollectGeneration 0012f2b8 7a07cba8 mscorwks!WKS::gc_heap::try_allocate_more_space+0x1a1 0012f2d4 7a07cefb mscorwks!WKS::gc_heap::allocate_more_space+0x18 0012f2f4 7a02a51b mscorwks!WKS::GCHeap::Alloc+0x4b 0012f310 7a02ae4c mscorwks!Alloc+0x60 0012f364 7a030e46 mscorwks!FastAllocatePrimitiveArray+0xbd 0012f424 300027f4 mscorwks!JIT_NewArr1+0x148 000af70f 3000299f fragment_ni!request..ctor(Int32, Single)+0x20c 0000002a 79fa22bd fragment_ni!request.Main(System.String[])+0x153
Just-In-Time ヘルパー (
JIT_New*
) は最終的にGCHeap::GarbageCollectGeneration
を呼び出します。 ジェネレーション 2 のガベージ コレクションが割り当てによって発生していると判断した場合は、ジェネレーション 2 ガベージ コレクションによって収集されるオブジェクトとその回避方法を決定する必要があります。 つまり、ジェネレーション 2 ガベージ コレクションの開始と終了、およびジェネレーション 2 コレクションの原因となったオブジェクトの違いを確認する必要があります。たとえば、デバッガーで次のコマンドを入力して、ジェネレーション 2 のコレクションの先頭を表示します。
!dumpheap –stat
出力例 (最も多くのスペースを使用するオブジェクトを示すために簡略化):
79124228 31857 9862328 System.Object[] 035f0384 25668 11601936 Toolkit.TlkPosition 00155f80 21248 12256296 Free 79103b6c 297003 13068132 System.Threading.ReaderWriterLock 7a747ad4 708732 14174640 System.Collections.Specialized.HybridDictionary 7a747c78 786498 15729960 System.Collections.Specialized.ListDictionary+DictionaryNode 7a747bac 700298 19608344 System.Collections.Specialized.ListDictionary 035f0ee4 89192 38887712 Toolkit.TlkOrder 00fcae40 6193 44911636 WaveBasedStrategy.Tick_Snap[] 7912c444 91616 71887080 System.Double[] 791242ec 32451 82462728 System.Collections.Hashtable+bucket[] 790fa3e0 2459154 112128436 System.String Total 6471774 objects
第 2 世代の最後にコマンドを繰り返します。
!dumpheap –stat
出力例 (最も多くのスペースを使用するオブジェクトを示すために簡略化):
79124228 26648 9314256 System.Object[] 035f0384 25668 11601936 Toolkit.TlkPosition 79103b6c 296770 13057880 System.Threading.ReaderWriterLock 7a747ad4 708730 14174600 System.Collections.Specialized.HybridDictionary 7a747c78 786497 15729940 System.Collections.Specialized.ListDictionary+DictionaryNode 7a747bac 700298 19608344 System.Collections.Specialized.ListDictionary 00155f80 13806 34007212 Free 035f0ee4 89187 38885532 Toolkit.TlkOrder 00fcae40 6193 44911636 WaveBasedStrategy.Tick_Snap[] 791242ec 32370 82359768 System.Collections.Hashtable+bucket[] 790fa3e0 2440020 111341808 System.String Total 6417525 objects
double[]
オブジェクトは出力の最後から消えました。つまり、オブジェクトが収集されました。 これらのオブジェクトは約 70 MB を占める。 残りのオブジェクトはあまり変更されませんでした。 そのため、これらのdouble[]
オブジェクトが、このジェネレーション 2 のガベージ コレクションが発生した理由でした。 次の手順では、double[]
オブジェクトが存在する理由と、そのオブジェクトが死んだ理由を判断します。 これらのオブジェクトの由来をコード開発者に問い合わせることもできますし、gcroot
コマンドを使用することもできます。
CPU 使用率の高さがガベージ コレクションによって発生しているかどうかを判断するには
% Time in GC
メモリ パフォーマンス カウンターの値と処理時間を関連付けます。% Time in GC
値が処理時間と同時に急増すると、ガベージ コレクションによって CPU 使用率が高くなります。 それ以外の場合は、アプリケーションをプロファイリングして、使用率が高い場所を見つけます。
こちらも参照ください
.NET