12

私たちのアプリケーションは、大量のデータ (たとえば、数十から数百メガバイト) に対して配列を継続的に割り当てます。これらのデータは、破棄されるまでの短い時間存続します。

これを単純に行うと、大きなオブジェクト ヒープの断片化が発生し、現在ライブ オブジェクトのサイズが過大ではないにもかかわらず、最終的に 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 フラグメンテーションを回避するアプローチは合理的ですか?
  • 現在見られているように見えるメモリの断片化を回避するための良い戦略はありますか?

私が考えることができる最も明白な答えは、メモリ チャンクをプールして再利用することです。これは実行できる可能性がありますが、メモリのその部分を自分で効果的に管理する必要があるため、むしろ避けたいと思います。

4

1 に答える 1