次の方法で共有


ガベージ コレクションとパフォーマンス

この記事では、ガベージ コレクションとメモリ使用量に関連する問題について説明します。 マネージド ヒープに関連する問題に対処し、アプリケーションに対するガベージ コレクションの影響を最小限に抑える方法について説明します。 各問題には、問題の調査に使用できる手順へのリンクがあります。

パフォーマンス分析ツール

次のセクションでは、メモリ使用量とガベージ コレクションの問題を調査するために使用できるツールについて説明します。 この記事で後述 する手順 は、これらのツールを参照します。

メモリ パフォーマンス カウンター

パフォーマンス カウンターを使用して、パフォーマンス データを収集できます。 手順については、「 ランタイム プロファイル」を参照してください。 パフォーマンス カウンターの .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 とメモリの使用状況を監視できます。 詳細については、「 アプリケーション ドメイン リソースの監視」を参照してください。

パフォーマンスに関する問題のトラブルシューティング

最初の手順では、 問題が実際にガベージ コレクションであるかどうかを判断します。 それが正しいと判断した場合は、次の一覧から選択して問題のトラブルシューティングを行います。

問題: メモリ不足例外がスローされる

マネージド 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 つのメモリ パフォーマンス カウンターを確認します。

    • GC で時刻を% します。 最後のガベージ コレクション サイクル後にガベージ コレクションの実行に費やされた経過時間の割合を表示します。 このカウンターを使用して、ガベージ コレクターがマネージド ヒープ領域を使用できるようにするために時間がかかりすぎるかどうかを判断します。 ガベージ コレクションに費やされる時間が比較的少ない場合は、マネージド ヒープの外部にあるリソースの問題を示している可能性があります。 同時実行ガベージ コレクションまたはバックグラウンド ガベージ コレクションが関係している場合、このカウンターは正確でない可能性があります。

    • # コミットされた合計バイト数。 ガベージ コレクターによって現在コミットされている仮想メモリの量を表示します。 このカウンターを使用して、ガベージ コレクターによって消費されるメモリが、アプリケーションで使用されるメモリの過剰な部分であるかどうかを判断します。

    ほとんどのメモリ パフォーマンス カウンターは、各ガベージ コレクションの終了時に更新されます。 そのため、情報が必要な現在の条件が反映されていない可能性があります。

メモリ不足例外が管理されているかどうかを確認するには

  1. SOS デバッガー拡張機能が読み込まれた WinDbg または Visual Studio デバッガーで、印刷例外 (pe) コマンドを入力します。

    !pe

    例外が管理されている場合、次の例に示すように、 OutOfMemoryException は例外の種類として表示されます。

    Exception object: 39594518
    Exception type: System.OutOfMemoryException
    Message: <none>
    InnerException: <none>
    StackTrace (generated):
    
  2. 出力で例外が指定されていない場合は、メモリ不足例外の元のスレッドを特定する必要があります。 デバッガーで次のコマンドを入力して、すべてのスレッドとその呼び出し履歴を表示します。

    ~\*kb

    例外呼び出しがあるスタックを持つスレッドは、 RaiseTheException 引数によって示されます。 これはマネージド例外オブジェクトです。

    28adfb44 7923918f 5b61f2b4 00000000 5b61f2b4 mscorwks!RaiseTheException+0xa0
    
  3. 次のコマンドを使用して、入れ子になった例外をダンプできます。

    !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
    

十分な物理メモリがあるかどうかを判断するには

  1. Windows タスク マネージャーを起動します。

  2. [ Performance ] タブで、コミットされた値を確認します。 (Windows 7 では、System groupCommit (KB)を参照してください)。

    TotalLimitに近い場合は、物理メモリが不足しています。

マネージド ヒープがコミットしているメモリの量を確認するには

  • # 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 メソッドを使用すると、ファイナライザーはタスクを完了し、問題を修正できます。

最終処理を待機しているオブジェクトがあるかどうかを判断するには

  1. SOS デバッガー拡張機能が読み込まれた WinDbg または Visual Studio デバッガーで、次のコマンドを入力します。

    !finalizequeue

    最終処理の準備ができているオブジェクトの数を確認します。 数値が大きい場合は、これらのファイナライザーがまったく進行できないか、または十分な速さで進行できない理由を調べる必要があります。

  2. スレッドの出力を取得するには、次のコマンドを入力します。

    !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 つのサンプリング間隔を示しています。 Gen0Gen1、および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 (GCSuspendEEEndGCSuspendEEBegin_V1) かかりました。

    実際のガベージ コレクションには 4.8 ミリ秒 (GCEnd_V1GCStart_V1) かかりました。

    マネージド スレッドの再開には 21us (GCRestartEEEndGCRestartEEBegin) かかりました。

    次の出力は、バックグラウンド ガベージ コレクションの例を示し、プロセス、スレッド、およびイベント フィールドを含みます。 (すべてのデータが表示されるわけではありません)。

    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 使用率が高くなります。 それ以外の場合は、アプリケーションをプロファイリングして、使用率が高い場所を見つけます。

こちらも参照ください