Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/306.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 我应该在使用大型对象堆后立即调用GC.Collect来防止碎片化吗_C#_Memory Management_Out Of Memory_Fragmentation_Large Object Heap - Fatal编程技术网

C# 我应该在使用大型对象堆后立即调用GC.Collect来防止碎片化吗

C# 我应该在使用大型对象堆后立即调用GC.Collect来防止碎片化吗,c#,memory-management,out-of-memory,fragmentation,large-object-heap,C#,Memory Management,Out Of Memory,Fragmentation,Large Object Heap,我的应用程序对大型对象进行了大量的二进制序列化和压缩。未压缩的序列化数据集约为14MB。压缩数据约为1.5 MB。我发现,每当我在数据集上调用serialize方法时,我的大型对象堆性能计数器就会从1 MB以下跳到90 MB左右。我还知道,在一个负载相对较重的系统中,通常在运行一段时间(几天)后(在这段时间内,这个序列化过程发生了几次),当调用这个序列化方法时,应用程序会抛出内存异常,尽管看起来内存充足。我猜碎片化是个问题(虽然我不能说我100%肯定,但我已经很接近了) 我能想到的最简单的短期修

我的应用程序对大型对象进行了大量的二进制序列化和压缩。未压缩的序列化数据集约为14MB。压缩数据约为1.5 MB。我发现,每当我在数据集上调用serialize方法时,我的大型对象堆性能计数器就会从1 MB以下跳到90 MB左右。我还知道,在一个负载相对较重的系统中,通常在运行一段时间(几天)后(在这段时间内,这个序列化过程发生了几次),当调用这个序列化方法时,应用程序会抛出内存异常,尽管看起来内存充足。我猜碎片化是个问题(虽然我不能说我100%肯定,但我已经很接近了)

我能想到的最简单的短期修复方法(我想我正在寻找短期和长期的答案)是在完成序列化过程后立即调用GC.Collect。在我看来,这将从LOH中对对象进行垃圾收集,并且很可能在其他对象添加到LOH之前进行垃圾收集。这将允许其他对象与堆中的其余对象紧密配合,而不会造成太多碎片

除了这个可笑的90MB分配之外,我认为我并没有任何其他使用lost of the LOH的方法。这种90 MB的分配也相对较少(每4小时分配一次)。当然,我们仍然会有1.5MB的数组,可能还有其他一些较小的序列化对象

有什么想法吗

由于响应良好而进行更新

这是我做这项工作的代码。实际上,我已经尝试在序列化时将其更改为压缩,以便序列化同时序列化为流,并且没有得到更好的结果。我还尝试将内存流预分配到100MB,并尝试连续两次使用同一个流,LOH无论如何会增加到180MB。我正在使用Process Explorer监视它。这太疯狂了。我想我下一步要尝试非托管内存流的想法

如果你们不愿意,我鼓励你们尝试一下。不一定是这个密码。只要序列化一个大数据集,你就会得到令人惊讶的结果(我的数据集有很多表,大约15个,还有很多字符串和列)

尝试使用非托管MemoryStream进行二进制序列化后更新

即使我序列化到一个非托管的内存流,LOH也会跳到相同的大小。似乎无论我做什么,调用BinaryFormatter来序列化这个大对象都将使用LOH。至于预分配,似乎帮助不大。假设我预先分配,假设我预先分配100MB,然后我序列化,它将使用170MB。这是代码。甚至比上面的代码更简单

BinaryFormatter serializer  = new BinaryFormatter();
MemoryStream memoryStream = new MemoryStream(1024*1024*100);
GC.Collect();
serializer.Serialize(memoryStream, assetDS);

GC .Cub()在中间只更新LOH性能计数器。您将看到它将分配正确的100MB。但是,当您调用serialize时,您会注意到它似乎将其添加到您已经分配的100个内存之上。

90MB的RAM并不多


除非有问题,否则避免调用GC.Collect。如果您有问题,并且没有更好的解决方法,请尝试调用GC.Collect,看看您的问题是否得到解决。

请注意.NET中的收集类和流(如MemoryStream)的工作方式。它们有一个底层缓冲区,一个简单的数组。每当集合或流缓冲区的大小超过分配的数组大小时,就会重新分配数组,现在是以前大小的两倍

这可能会导致LOH中存在多个阵列副本。您的14MB数据集将开始使用128KB的LOH,然后再使用256KB,然后再使用512KB,等等。最后一个,即实际使用的,大约为16MB。LOH包含了这些数据的总和,约为30MB,其中只有一个在实际使用中

在不使用gen2集合的情况下执行三次,LOH将增长到90MB


通过将缓冲区预分配到预期大小来避免这种情况。MemoryStream有一个接受初始容量的构造函数。所有集合类也是如此。在清空所有引用后调用GC.Collect()可以帮助清除LOH并清除这些中间缓冲区,代价是过早地阻塞gen1和gen2堆。

如果您确实需要将LOH用于服务或需要长时间运行的内容,您需要使用从不释放的缓冲池,并且在理想情况下可以在启动时进行分配。当然,这意味着您必须自己进行“内存管理”

根据您对该内存所做的操作,您可能还必须对选定部分的本机代码进行p/Invoke,以避免必须调用某些.NET API来强制您将数据放在LOH中新分配的空间中

这是一篇关于以下问题的良好起点文章:

我认为你很幸运,如果GC窍门会起作用,如果系统中同时没有太多的事情,它只会起作用。如果你的工作是并行进行的,这只会稍微推迟不可避免的事情


另外,请阅读关于GC.Collect.IIRC的文档,GC.Collect(n)只说它只收集了n代以外的数据——并不是说它实际上已经收集到了n代数据。

不要担心LOH的大小会急剧增加。担心分配/取消分配LOH。Net对于LOH非常愚蠢——它不是在远离常规堆的地方分配LOH对象,而是在下一个可用的VM页面上进行分配。我有一个3D应用程序,它对LOH和常规对象进行了大量的分配/取消分配——结果(如DebugDiag dump报告中所示)是,小堆和大堆的页面在整个RAM中交替出现,直到没有大的应用程序块剩下2GB的VM空间。如果可能的话,解决方案是只分配一次您需要的,然后不要发布它——下次再使用它

使用DebugDiag
BinaryFormatter serializer  = new BinaryFormatter();
MemoryStream memoryStream = new MemoryStream(1024*1024*100);
GC.Collect();
serializer.Serialize(memoryStream, assetDS);