我已阅读有关世代和大型对象堆的信息。但是我仍然不明白拥有大型对象堆的意义(或好处)是什么?
如果 CLR 仅仅依靠第 2 代(考虑到 Gen0 和 Gen1 的阈值很小,无法处理大对象)来存储大对象,会出现什么问题(在性能或内存方面)?
我已阅读有关世代和大型对象堆的信息。但是我仍然不明白拥有大型对象堆的意义(或好处)是什么?
如果 CLR 仅仅依靠第 2 代(考虑到 Gen0 和 Gen1 的阈值很小,无法处理大对象)来存储大对象,会出现什么问题(在性能或内存方面)?
垃圾回收不仅会清除未引用的对象,还会压缩堆。这是一个非常重要的优化。它不仅使内存使用效率更高(没有未使用的漏洞),而且使 CPU 缓存更高效。缓存在现代处理器上非常重要,它们比内存总线快一个数量级。
压缩只是通过复制字节来完成。然而,这需要时间。对象越大,复制它的成本就越有可能超过可能的 CPU 缓存使用改进。
因此,他们运行了一系列基准测试来确定盈亏平衡点。并达到 85,000 字节作为复制不再提高性能的截止点。除了 double 数组的特殊例外,当数组有超过 1000 个元素时,它们被认为是“大”的。这是对 32 位代码的另一种优化,大对象堆分配器具有特殊属性,它在对齐到 8 的地址分配内存,这与仅分配对齐到 4 的常规分代分配器不同。这种对齐对于 double 来说很重要,读取或写入未对齐的双精度非常昂贵。奇怪的是,稀疏的微软信息从未提到过长数组,不知道这是怎么回事。
Fwiw,有很多程序员担心大型对象堆没有被压缩。当他们编写的程序消耗了整个可用地址空间的一半以上时,这总是会被触发。随后使用诸如内存分析器之类的工具来找出程序被炸毁的原因,即使仍有大量未使用的虚拟内存可用。这样的工具显示了 LOH 中的漏洞,未使用的内存块,以前有一个大对象存在,但被垃圾收集了。这就是 LOH 不可避免的代价,这个洞只能通过分配给大小相等或更小的对象来重复使用。真正的问题是假设应该允许程序随时消耗所有虚拟内存。
仅通过在 64 位操作系统上运行代码即可完全消失的问题。64 位进程有8 TB的可用虚拟内存地址空间,比 32 位进程多 3 个数量级。你只是不能用完孔。
长话短说,LOH 使代码运行更高效。代价是使用可用的虚拟内存地址空间效率较低。
更新,.NET 4.5.1 现在支持压缩 LOH、GCSettings.LargeObjectHeapCompactionMode属性。请注意后果。
如果对象的大小大于某个固定值(.NET 1 中为 85000 字节),则 CLR 将其放入大对象堆中。这优化了:
原则是,一个进程不太可能(并且很可能是糟糕的设计)会创建大量寿命短的大对象,因此 CLR 将大对象分配给一个单独的堆,在该堆上它按照与常规堆不同的计划运行 GC。http://msdn.microsoft.com/en-us/magazine/cc534993.aspx
我不是 CLR 方面的专家,但我想为大型对象提供专用堆可以防止对现有分代堆进行不必要的 GC 扫描。分配大对象需要大量连续的可用内存。为了从世代堆中分散的“洞”中提供这一点,您需要频繁的压缩(仅在 GC 周期中完成)。