私たちのアプリケーションは、大量のデータ (たとえば、数十から数百メガバイト) に対して配列を継続的に割り当てます。これらのデータは、破棄されるまでの短い時間存続します。
これを単純に行うと、大きなオブジェクト ヒープの断片化が発生し、現在ライブ オブジェクトのサイズが過大ではないにもかかわらず、最終的に OutOfMemoryException でアプリケーションがクラッシュする可能性があります。
過去にこれをうまく管理してきた 1 つの方法は、配列をチャンクアップして、最終的に LOH にならないようにすることです。これは、ガベージ コレクターによってメモリを圧縮できるようにすることで断片化を回避するという考え方です。
最新のアプリケーションは、以前よりも多くのデータを処理し、このシリアル化されたデータを、別の AppDomains または別のプロセスでホストされているアドイン間で非常に頻繁に渡します。以前と同じアプローチを採用し、メモリが常にチャンク化されていることを確認し、大きなオブジェクト ヒープの割り当てを避けるように細心の注意を払いました。
ただし、外部の 32 ビット プロセスでホストする必要があるアドインが 1 つあります (メイン アプリケーションは 64 ビットであり、アドインは 32 ビット ライブラリを使用する必要があるため)。特に重い負荷の下で、大量の SOH メモリ チャンクがすばやく割り当てられ、すぐに破棄されると、チャンク アプローチでさえ 32 ビット アドインを保存するには不十分であり、OutOfMemoryException でクラッシュします。
OutOfMemoryException が発生した瞬間に WinDbg を使用すると、次のようになり!heapstat -inclUnrooted
ます。
Heap Gen0 Gen1 Gen2 LOH
Heap0 24612 4166452 228499692 9757136
Free space: Percentage
Heap0 12 12 4636044 12848SOH: 1% LOH: 0%
Unrooted objects: Percentage
Heap0 72 0 5488 0SOH: 0% LOH: 0%
!dumpheap -stat
これを見せてください:
-- SNIP --
79b56c28 3085 435356 System.Object[]
79b8ebd4 1 1048592 System.UInt16[]
79b9f9ac 26880 1301812 System.String
002f7a60 34 4648916 Free
79ba4944 6128 87366192 System.Byte[]
79b8ef28 17195 145981324 System.Double[]
Total 97166 objects
Fragmented blocks larger than 0.5 MB:
Addr Size Followed by
18c91000 3.7MB 19042c7c System.Threading.OverlappedData
これらは、メモリ使用量が過度ではなく、大きなオブジェクト ヒープが予想どおり非常に小さいことを示しています (したがって、ここでは大きなオブジェクト ヒープの断片化を扱っていません)。
しかし、!eeheap -gc
これを示しています:
Number of GC Heaps: 1
generation 0 starts at 0x7452b504
generation 1 starts at 0x741321d0
generation 2 starts at 0x01f91000
ephemeral segment allocation context: none
segment begin allocated size
01f90000 01f91000 02c578d0 0xcc68d0(13396176)
3cb10000 3cb11000 3d5228b0 0xa118b0(10557616)
3ece0000 3ece1000 3fc2ef48 0xf4df48(16047944)
3db10000 3db11000 3e8fc8f8 0xdeb8f8(14596344)
42e20000 42e21000 4393e1f8 0xb1d1f8(11653624)
18c90000 18c91000 19c53210 0xfc2210(16523792)
14c90000 14c91000 15c85c78 0xff4c78(16731256)
15c90000 15c91000 168b2870 0xc21870(12720240)
16c90000 16c91000 17690744 0x9ff744(10483524)
5c0c0000 5c0c1000 5d05381c 0xf9281c(16328732)
69c80000 69c81000 6a88bc88 0xc0ac88(12627080)
6b2d0000 6b2d1000 6b83e8a0 0x56d8a0(5691552)
6c2d0000 6c2d1000 6d0f2608 0xe21608(14816776)
6d2d0000 6d2d1000 6defc67c 0xc2b67c(12760700)
6e2d0000 6e2d1000 6ee7f304 0xbae304(12247812)
70000000 70001000 70bfb41c 0xbfa41c(12559388)
71ca0000 71ca1000 72893440 0xbf2440(12526656)
73b40000 73b41000 74531528 0x9f0528(10421544)
Large object heap starts at 0x02f91000
segment begin allocated size
02f90000 02f91000 038df1d0 0x94e1d0(9757136)
Total Size: Size: 0xe737614 (242447892) bytes.
------------------------------
GC Heap Size: Size: 0xe737614 (242447892) bytes.
ここで私を驚かせたのは、最後の SOH ヒープ セグメントが 0x73b41000 で始まっていることです。これは、32 ビット アドインで使用可能なメモリの限界に達しています。
したがって、私がそれを正しく読んでいる場合、私たちの問題は、仮想メモリがマネージ ヒープ セグメントで断片化されていることのようです。
ここでの私の質問は次のようになると思います。
- 私の分析は正しいですか?
- チャンキングを使用して LOH フラグメンテーションを回避するアプローチは合理的ですか?
- 現在見られているように見えるメモリの断片化を回避するための良い戦略はありますか?
私が考えることができる最も明白な答えは、メモリ チャンクをプールして再利用することです。これは実行できる可能性がありますが、メモリのその部分を自分で効果的に管理する必要があるため、むしろ避けたいと思います。