大阵列和LOH碎片。什么是公认的惯例?

时间:2010-03-08 15:15:41

标签: c# arrays large-object-heap

我还有另一个活跃的问题HERE关于一些可能涉及LOH碎片的无望记忆问题,可能还有其他未知因素。

我现在的问题是,接受的做事方式是什么? 如果我的应用程序需要在Visual C#中完成,并且需要处理大型数组到int [4000000]的调整,那么我怎么能注意垃圾收集器拒绝处理LOH ?

似乎我被迫将任何大型数组全局化,并且从不在它们周围使用“new”这个词。所以,我留下了带有“maxindex”变量的不合适的全局数组,而不是由函数传递的整齐大小的数组。

我一直被告知这是不好的做法。还有什么替代方案?

System.GC.CollectLOH("Seriously")的曲调是否有某种功能? 有没有办法将垃圾收集外包给System.GC以外的其他东西?

无论如何,处理大型(> 85Kb)变量的普遍接受的规则是什么?

6 个答案:

答案 0 :(得分:26)

首先,垃圾收集器 收集LOH,所以不要立即被它的早期吓到。收集第2代时会收集LOH。

不同之处在于LOH没有被压缩,这意味着如果你有一个具有长寿命的物体,那么你将有效地将LOH分成两个部分 - 前面的区域和该物体之后的区域。如果这种情况继续发生,那么你可能会遇到这样的情况,即长期存在的对象之间的空间不足以进行后续分配,并且.NET必须分配越来越多的内存才能放置大对象,即LOH变得支离破碎。

现在,话虽如此,如果LOH的末端区域完全没有活物,那么LOH的尺寸会缩小,所以唯一的问题是如果你把物体放在那里很长时间(例如应用的持续时间) )。

从.NET 4.5.1开始,可以压缩LOH,请参阅GCSettings.LargeObjectHeapCompactionMode属性。

避免LOH碎片的策略是:

  • 避免创建悬挂的大型对象。基本上这只是意味着大型数组或包装大型数组的对象(例如包装字节数组的MemoryStream),因为没有其他东西那么大(复杂对象的组件分别存储在堆上,所以很少很大)。还要注意大词典和列表,因为它们在内部使用数组。
  • 注意双阵列 - 这些进入LOH的门槛要小得多,要小得多 - 我不记得确切的数字,但只有几千个。
  • 如果你需要一个MemoryStream,考虑制作一个支持多个较小阵列而不是一个大阵列的分块版本。您还可以使用分块制作IList和IDictionary的自定义版本,以避免首先在LOH中结束。
  • 避免很长的Remoting呼叫,因为Remoting大量使用MemoryStreams,它可以在呼叫期间分割LOH。
  • 注意字符串实习 - 由于某些原因,这些存储为LOH上的页面,如果您的应用程序继续遇到新的字符串实习,可能会导致严重的碎片,即避免使用string.Intern,除非已知字符串集是有限的,在应用程序的生命早期遇到全套。 (见my earlier question。)
  • 使用罢工之子来查看使用LOH内存究竟是什么。有关如何执行此操作的详细信息,请再次参阅this question
  • 考虑pooling large arrays

编辑:双数组的LOH阈值似乎是8k。

答案 1 :(得分:8)

这是一个老问题,但我认为用.NET中引入的更改更新答案并没有什么坏处。现在可以对大对象堆进行碎片整理。显然,首选应该是确保做出最佳设计选择,但现在有这个选项很好。

https://msdn.microsoft.com/en-us/library/xe0c2357(v=vs.110).aspx

“从.NET Framework 4.5.1开始,您可以通过在调用Collect方法之前将GCSettings.LargeObjectHeapCompactionMode属性设置为GCLargeObjectHeapCompactionMode.CompactOnce来压缩大对象堆(LOH),如以下示例所示。”

可以在System.Runtime命名空间

中找到GCSettings
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect(); 

答案 2 :(得分:7)

首先要想到的是将阵列分成较小的阵列,这样它们就无法到达GC所需的内存来放入LOH。你可以把数组吐成10,000个较小的数组,并根据你传递的索引器构建一个知道要查看哪个数组的对象。

现在我还没有看到代码,但我也会质疑你为什么需要一个大的数组。我可能会考虑重构代码,以便所有这些信息不需要一次存储在内存中。

答案 3 :(得分:6)

你弄错了。你不需要有4000000的数组大小,你绝对不需要调用garbace收集器。

  • 编写自己的IList实现。喜欢“PagedList”
  • 将项目存储在65536个元素的数组中。
  • 创建一个数组数组来保存页面。

这允许您仅使用一个重定向访问基本上所有元素。并且,由于单个阵列较小,碎片不是问题......

...如果是......那么REUSE页面。不要把它们扔掉处理掉,把它们放在一个静态的“PageList”上并先从那里拉出来。所有这些都可以在你的课堂上透明地完成。

真正的好处是这个List在内存使用方面非常动态。您可能想要调整holder数组(重定向器)的大小。即使没有,它也只是每页512kbdata。

第二级数组每个字节基本上有64k - 一个类是8字节(每页512kb,32位256kb),或每个结构字节64kb。

技术上:

打开     INT [] 成     INT [] []

根据需要确定32位或64位是否更好;)两者都有优点和缺点。

处理一个像这样的大型阵列在任何语言中都是不可思议的 - 如果你愿意,那么......基本上......在程序启动时分配,永远不会重新创建。只有解决方案。

答案 4 :(得分:0)

在问题如何产生方面,我对上述答案进行了详细阐述。 LOH的碎片不仅取决于长寿命的对象,而且如果你有多个线程并且每个都创建大型列表进入LOH的情况,那么你可能会遇到第一个线程的情况需要增加它的List但是下一个连续的内存位已经被第二个线程中的List占用,因此运行时将为第一个线程List分配新的内存 - 留下一个相当大的漏洞。这是目前在我继承的一个项目上发生的事情,因此即使LOH大约为4.5 MB,运行时总共有117 MB的可用内存,但最大的可用内存段是28MB。

如果没有多个线程,这种情况可能发生的另一种方式是,如果你在某种循环中添加了多个列表,并且每个列表都扩展到最初分配给它的内存之外,那么随着它们的增长,每个都会超越另一个他们分配的空间。

有用的链接是:https://www.simple-talk.com/dotnet/.net-framework/the-dangers-of-the-large-object-heap/

仍在为此寻找解决方案,一个选项可能是使用某种池化对象并在执行工作时从池中请求。如果您正在处理大型阵列,那么另一种选择是开发自定义集合,例如集合的集合,这样你就不会只有一个巨大的列表,而是将它分解成更小的列表,每个列表都避免使用LOH。

答案 5 :(得分:0)

这是一个古老的问题,但是对于.NET Standard 1.1(.NET Core,.NET Framework 4.5.1+),还有另一种可能的解决方案:

使用ArrayPool<T>包中的System.Buffers,我们可以对数组进行池化处理,以避免出现此问题。