.NET ガベージコレクションと仮想メモリの関係およびヒープ健全性の診断

ガベージコレクタ(GC)は、アプリケーションの仮想メモリと物理メモリの状態に密接に関連しており、特に OutOfMemoryException が発生する原因を理解する上で重要です。この記事では、GC とメモリ管理の関係、およびマネージドヒープの健全性を評価する方法について解説します。

GC と仮想メモリ・物理メモリの関係

GC は内部で「セグメント」と呼ばれる単位でメモリを確保します。この確保処理には Windows API の VirtualAlloc が使用されるため、プロセスの仮想アドレス空間に十分な連続領域が存在しない場合、割り当てが失敗し、結果として OutOfMemoryException がスローされます。注意すべき点として、例外を実際に投げるのは GC 自体ではなく、CLR の実行エンジンです。GC は単に割り当て失敗時に NULL を返すだけです。

よくある質問として、「マネージドヒープが数100MBしか使っていないのに、なぜ OOM が発生するのか?」というものがあります。これは、.NET アプリケーションが GC ヒープ以外にも多くの仮想メモリを使用しているためです。たとえば以下のような要素が仮想アドレス空間を消費します:

  • ロードされた DLL やネイティブモジュール
  • ネイティブコードによる直接的なメモリ確保(VirtualAlloc, HeapAlloc, C++ の new など)
  • CLR 内部構造(JIT コンパイル済みコード、メタデータテーブルなど)

これらの使用量は通常小さいですが、累積すると大きな影響を及ぼします。SOS 拡張コマンドの !eeheap を使うと、CLR が使用しているメモリの内訳を確認できます。例として、次のような出力が得られます:

GC Heap Size  0x21ead7ac(569038764) [約540MB]
Total LoaderHeap size: 0x1e5000(1986560)bytes [<2MB]

一方で、仮想メモリの空き領域を確認すると、最大の連続空きブロックが 60MB 未満であることが判明しました:

0:119> !address
Largest free region: Base 54000000 - Size 03b60000
Evaluate expression: 62259200 = 03b60000

Server GC モードでは、新しいセグメントのサイズは通常 64MB であるため、この状況では割り当てが不可能となり、OOM が発生します。特に、頻繁に DLL をロード/アンロードするアプリケーション(例:COM コンポーネントや多数の小さな ASP.NET アセンブリ)では、仮想メモリの断片化が深刻な問題になります。

なお、GC は物理メモリとは直接関係ありませんが、システム全体の物理メモリが逼迫すると、GC はより積極的に回収を実行します。このとき、第0世代、第1世代、第2世代の回収頻度がほぼ同等になる傾向があり、パフォーマンスに悪影響を及ぼします。また、64ビット環境では仮想アドレス空間が広大なため、物理メモリがボトルネックになる可能性があります。

マネージドヒープの健全性を評価する方法

ヒープの状態を正確に把握するには、以下の手法が有効です:

  1. パフォーマンスカウンターの高頻度記録:1秒間隔で数分間計測することで、短期的な割り当てパターンを捉えられます。
  2. 完全ダンプの取得:MiniDump では情報が不足するため、フルメモリダンプが必要です。
  3. CLRProfiler の使用:リアルタイムでオブジェクトのライフサイクルを可視化できますが、プロファイリング負荷が高いため注意が必要です。

重要な評価指標

1. GC 時間比率(% Time in GC)
この値が高い場合、特に第2世代の回収が頻繁または長時間実行されていることを示唆します。割り当てパターンの見直しが必要です。

2. ヒープの成長傾向
長時間稼働するサービスでヒープサイズが継続的に増加している場合、メモリリークの可能性があります。特に # of GC Handles カウンターが増加し続ける場合は、ハンドルリークが疑われます。

3. 各世代の回収頻度比
健全なアプリケーションでは、第2世代 : 第1世代 ≒ 1 : 10 が目安です。1:1 に近い場合は異常であり、不要なオブジェクトが第2世代まで到達している可能性があります。

4. ヒープの断片化
断片化は !dumpheap -type Free -stat で確認できます。各領域ごとの特性は以下の通りです:

  • 第0世代:断片化はむしろ望ましい。新規オブジェクトの割り当てで再利用されるため。
  • 大オブジェクトヒープ(LOH):コンパクションされないため断片化は避けられないが、解放された領域は結合されて再利用される。
  • 第1・第2世代:断片化は深刻な問題。回収後も大量の空き領域が残っている場合、割り当て戦略に問題がある。

第2世代の断片化率が 20% 未満であれば、良好とみなせます。

タグ: .NET GarbageCollection MemoryManagement CLR PerformanceAnalysis

9月13日 12:37 投稿