15

我有以下代码:

const int bufferSize = 1024 * 1024;
var buffer = new byte[bufferSize];
for (int i = 0; i < 10; i++)
{
    const int writesCount = 400;
    using (var stream = new MemoryStream(writesCount * bufferSize))
    {
        for (int j = 0; j < writesCount; j++)
        {
            stream.Write(buffer, 0, buffer.Length);
        }
        stream.Close();
    }
}

我在 32 位机器上运行。

第一次迭代完成得很好,然后在下一次迭代中,我System.OutOfMemoryException在.newMemoryStream

尽管声明,为什么没有MemoryStream回收以前的内存?using如何强制释放使用的内存MemoryStream

4

4 回答 4

14

我认为问题不在于垃圾收集器没有做好它的工作。如果 GC 处于内存压力之下,它应该运行并回收您刚刚分配的 400 MB。

这更有可能归结为 GC 没有找到连续的400 MB块。

相反,会发生“内存不足”错误,因为进程无法在其虚拟地址空间中找到足够大的连续未使用页面部分来执行请求的映射。

您应该阅读 Eric Lippert 的博客文章“内存不足”不涉及物理内存

你最好做以下两个

  1. 重用您分配的内存块(为什么要创建另一个大小完全相同的内存块)
  2. 分配更小的块(小于85KB

在 Dotnet 4.5 之前,Dotnet 构建了两个堆,小对象堆 (SOH)大对象堆 (LOH)。请参阅Brandon Bray的 .NET 4.5 中的大型对象 Hearp 改进。MemoryStream在 LOH 中被分配,并且在整个过程中没有被压缩(碎片整理),这使得分配大量内存的多次调用更有可能引发OutOfMemoryException

CLR 管理两个不同的分配堆,小对象堆 (SOH) 和大对象堆 (LOH)。任何大于或等于 85,000 字节的分配都会在 LOH 上进行。复制大对象会降低性能,因此 LOH 不像 SOH 那样被压缩。另一个定义特征是 LOH 仅在第 2 代收集期间收集。总之,这些都有一个内置的假设,即大对象分配不常见。

于 2012-08-17T08:48:02.497 回答
1

当您确定有必要清理未引用的对象时,请尝试强制进行垃圾收集。

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

另一种选择是使用Stream外部存储:FileStream例如。

但是,在一般情况下,最好使用一个足够小的缓冲区(数组,分配一次)并将其用于读/写调用。避免在 .NET中包含许多大对象(请参阅CLR Inside Out:Large Object Heap Uncovered)。

更新

假设writesCount是常数,为什么不分配一个缓冲区并重用它呢?

const int bufferSize = 1024 * 1024;
const int writesCount = 400;

byte[] streamBuffer = new byte[writesCount * bufferSize];
byte[] buffer = new byte[bufferSize];
for (int i = 0; i < 10; i++)
{
    using (var stream = new MemoryStream(streamBuffer))
    {
        for (int j = 0; j < writesCount; j++)
        {
            stream.Write(buffer, 0, buffer.Length);
        }
    }
}
于 2012-08-17T08:39:36.927 回答
1

看起来您分配的太多,超出了系统的处理能力。您的代码在我的机器上运行良好,但如果我像这样更改它:

const int bufferSize = 1024 * 1024 * 2;

我得到和你一样的错误。

但是,如果我将目标处理器更改为 x64,那么代码就会运行,这似乎是合乎逻辑的,因为您可以处理更多内存。

这篇文章的详细解释:http ://www.guylangston.net/blog/Article/MaxMemory 关于这个问题的一些信息:Maximum Memory a .NET process can allocate

于 2012-08-17T08:45:51.267 回答
1

首先,Dispose()不保证会释放内存(它不标记对象进行 GC 收集,以防MemoryStream- 它不释放任何内容,因为MemoryStream没有非托管资源)。释放所使用的内存的唯一可靠方法MemoryStream是丢失对它的所有引用并等待垃圾收集发生(如果你有OutOfMemoryException- 垃圾收集器已经尝试但未能释放足够的内存)。此外,分配如此大的对象(任何 > 85000 字节)会产生一些后果 - 这些对象将进入大对象堆 (LOH),这可能会变得碎片化(并且无法压缩)。由于 .NET 对象必须占用连续的字节序列,这可能会导致您有足够的内存,但没有空间容纳大对象。在这种情况下,垃圾收集器无济于事。

似乎这里的主要问题是对stream对象的引用保留在堆栈上,从而阻止了stream对象的垃圾收集(即使强制垃圾收集也无济于事,因为 GC 认为该对象仍然存在,您可以检查这个创建WeakRefrence它)。重构此示例可以修复它:

    static void Main(string[] args)
    {
        const int bufferSize = 1024 * 1024 * 2;
        var buffer = new byte[bufferSize];
        for(int i = 0; i < 10; i++)
        {
            const int writesCount = 400;
            Write(buffer, writesCount, bufferSize);
        }
    }

    static void Write(byte[] buffer, int writesCount, int bufferSize)
    {
        using(var stream = new MemoryStream(writesCount * bufferSize))
        {
            for(int j = 0; j < writesCount; j++)
            {
                stream.Write(buffer, 0, buffer.Length);
            }
        }
    }

这是一个证明对象不能被垃圾收集的示例:

    static void Main(string[] args)
    {
        const int bufferSize = 1024 * 1024 * 2;
        var buffer = new byte[bufferSize];
        WeakReference wref = null;
        for(int i = 0; i < 10; i++)
        {
            if(wref != null)
            {
                // force garbage collection
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                // check if object is still alive
                Console.WriteLine(wref.IsAlive); // true
            }
            const int writesCount = 400;
            using(var stream = new MemoryStream(writesCount * bufferSize))
            {
                for(int j = 0; j < writesCount; j++)
                {
                    stream.Write(buffer, 0, buffer.Length);
                }
                // weak reference won't prevent garbage collection
                wref = new WeakReference(stream);
            }
        }
    }
于 2012-08-17T08:58:52.587 回答