在C#中大型,快速和频繁的内存分配避免OutOfMemoryException异常(Avoiding

2019-06-27 02:05发布

我们的应用程序连续分配了大量数据的阵列(说几十到几百兆),它被丢弃之前住的时间比较短的量。

天真地这样做可能会导致大对象堆碎片,最终导致同一个OutOfMemoryException应用程序崩溃,尽管不是过多的当前活动对象的大小。

我们在过去已经成功地管理这样的一种方式是块组成的阵列,以确保它们不会对LOH,这个想法是允许的内存由垃圾收集器进行压缩以避免碎片结束。

我们最新的应用程序处理比以前更多的数据,在上述任何一种单独的AppDomain或单独的进程中托管加载项之间非常频繁通过这个序列化的数据。 我们像以前一样采取了同样的方法,以确保我们的记忆总是分块,并非常小心,以避免大型对象堆分配。

但是我们有一个外接必须在外部32位进程托管(因为我们的主要应用是64位和外接必须使用32位的库)。 在特别重的负载,当很多SOH内存块被快速分配和废弃后不久,甚至我们的分块方法还不足以拯救我们的32位插件并将其与一个OutOfMemoryException崩溃。

使用的WinDbg在当一个OutOfMemoryException发生的那一刻, !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位插件的限制。

所以,如果我正确读取,我们的问题似乎是我们的虚拟内存已产生碎片与托管堆段。

我想我的问题,这里将是:

  • 是我的分析是正确的?
  • 我们用分块合理避免蕙碎片的方法呢?
  • 是否有一个很好的策略,以避免我们现在似乎看到内存碎片?

最明显的答案,我能想到的是集中和重新使用我们的内存块。 这是有可能做到,能干,但事情我宁愿避免,因为它涉及到我们有效地管理我们的记忆中自己的那部分。

Answer 1:

对于那些有兴趣,这里是我发现了什么有关于这个问题的更新:

看来,最好的解决办法是执行我们的块池,以减轻对垃圾收集器的压力,所以我做到了这一点。

其结果是,加载了小幅进一步在它的任务,但不幸的是它仍然跑出的内存相当迅速。

再次看在WinDbg中,唯一真正的区别我可以看到的是,我们的联合托管堆大小比250MB左右前池始终较小,约为200MB。

这是几乎一样,如果提供给.NET内存量随时间减少,因此实现统筹只是简单地推迟运行内存。

如果这是真的明显的罪魁祸首是我们用它来将数据加载到内存中的COM组件。 我们做COM的一些缓存对象来提升重复访问时间数据。 我删除了所有的缓存,并确保一切都在数据的每个查询后释放。

现在,一切都看起来不错至于内存,它只是慢得多(我旁边都会解决)。

我想在事后COM组件应该一直为内存问题首先怀疑,但嘿,我学到了一些东西:)而在正侧,汇集仍然会降低GC开销有用的,所以这是值得做的事情为好。

谢谢大家的评论。



文章来源: Avoiding OutOfMemoryException during large, fast and frequent memory allocations in C#