大对象堆浪费

Asi*_*sik 13 .net c#

我注意到我的应用程序内存耗尽比它应该更快.它创建了许多每个几兆字节的字节数组.但是,当我查看vmmap的内存使用情况时,似乎.NET为每个缓冲区分配的内容远远超过了需要.确切地说,当分配9兆字节的缓冲区时,.NET会创建一个16兆字节的堆.剩余的7兆字节不能用于创建另一个9兆字节的缓冲区,因此.NET会创建另外的16兆字节.所以每个9MB缓冲区浪费7MB的地址空间!

这是一个示例程序,它在32位.NET 4中分配106个缓冲区后抛出OutOfMemoryException:

using System.Collections.Generic;

namespace CSharpMemoryAllocationTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var buffers = new List<byte[]>();
            for (int i = 0; i < 130; ++i)
            {
                buffers.Add(new byte[9 * 1000 * 1024]);
            }

        }
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,您可以将阵列的大小增加到16*1000*1024,并在内存不足之前仍然分配相同数量的缓冲区.

VMMap显示了这个:

在此输入图像描述

另请注意,托管堆的总大小与总通信大小之间几乎有100%的差异.(1737MB vs 946MB).

有没有一种可靠的方法解决.NET上的这个问题,即我可以强制运行时分配不超过我实际需要的,或者可能用于几个连续缓冲区的更大的托管堆?

Bri*_*sen 6

在内部,CLR在段中分配内存.从您的描述中可以看出,16 MB分配是段,您的阵列是在这些段中分配的.剩下的空间是保留的,在正常情况下不会真正浪费,因为它将用于其他分配.如果您没有任何适合剩余块的分配,则这些分配基本上是开销.

由于您的数组是使用连续内存分配的,因此您只能使用一个段中的一个,因此在这种情况下会产生开销.

默认段大小为16 MB,但如果分配大于该值,则CLR将分配更大的段.我不知道细节,但例如,如果我分配20 MB Wmmap显示我24 MB的段.

减少开销的一种方法是在可能的情况下进行符合段大小的分配.但请记住,这些是实现细节,可能会随着CLR的任何更新而改变.


Dom*_*icz 3

CLR 一次性从操作系统保留了 16MB 块,但只主动占用了 9MB。

我相信您期望 9MB 和 9MB 位于一堆中。困难在于变量现在被分成 2 个堆。

 Heap 1 = 9MB + 7MB
 Heap 2 = 2MB
Run Code Online (Sandbox Code Playgroud)

我们现在遇到的问题是,如果原始文件9MB被删除,我们现在有 2 个堆无法清理,因为内容是在堆之间共享的。

为了提高性能,方法是将它们放在单个堆中。

如果您担心内存使用情况,请不要担心。对于.NET来说,内存使用并不是一件坏事,因为如果没有人使用它,有什么问题呢?GC 将在某个时刻启动,内存将被清理。GC只会启动其中一个

  1. 当CLR认为有必要时
  2. 当操作系统告诉 CLR 归还内存时
  3. 当被代码强制时

但内存使用情况,尤其是在本例中,不应成为问题。内存使用会停止 CPU 周期的发生。否则,如果它不断地清理内存,您的 CPU 就会很高,并且您的进程(以及计算机上的所有其他进程)的运行速度会慢得多。