代码之家  ›  专栏  ›  技术社区  ›  sharptooth

如何强制释放MemoryStream占用的内存?

  •  16
  • sharptooth  · 技术社区  · 14 年前

    我有以下代码:

    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 行上的异常 new 是的 MemoryStream 。

    为什么前一个不是 内存流 内存已回收,尽管 using 陈述如何强制释放 内存流 ?

    4 回复  |  直到 14 年前
        1
  •  16
  •   Joel Coehoorn    5 年前

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

    这更有可能是因为GC没有找到 惊人的400 MB 块

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

    你应该看看Eric Lippert的博客 "Out Of Memory" Does Not Refer to Physical Memory

    你做得更好 二者都 以下的。

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

    在Dotnet 4.5之前, 小型对象堆(SOH) 和 大型对象堆(LOH) 看见 Large Object Hearp Improvements in .NET 4.5 布兰登·布雷。你的 MemoryStream 正在LOH中分配,并且在进程的持续时间内没有压缩(碎片整理),这使得分配如此大量内存的多个调用更有可能引发 OutOfMemoryException

    CLR管理两个不同的堆进行分配,即小对象 堆(SOH)和大对象堆(LOH)。任何大于 LOH上的字节数大于或等于85000。复制大型对象 具有性能损失,因此LOH不像SOH那样紧凑。 另一个定义性特征是LOH仅被收集 在第2代收集期间。加在一起,这些都有内置的 假设大对象分配是罕见的。

        2
  •  1
  •   Community Mohan Dere    9 年前

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

    const int bufferSize = 1024 * 1024 * 2;
    

    我和你犯了同样的错误。

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

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

        3
  •  1
  •   max    14 年前

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

    这里的主要问题似乎是 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);
                }
            }
        }
    
        4
  •  1
  •   Sergey Vyacheslavovich Brunov prodigitalson    14 年前

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

    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);
            }
        }
    }