如何强制MemoryStream占用释放内存?
我有以下代码:
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位机器上运行。
第一次迭代完成就好了,然后在下一次迭代中,我在MemoryStream
new
行上得到一个System.OutOfMemoryException
exception。
尽管using
语句,为什么不回收先前的MemoryStream
内存? 如何强制释放MemoryStream
使用的内存?
我不认为问题是垃圾收集器没有完成它的工作。 如果GC处于内存压力下,它应该运行并回收刚刚分配的400 MB。
这更有可能归结为GC没有找到一个有名的400 MB块。
相反,发生“内存不足”错误是因为进程无法在其虚拟地址空间中找到足够大的连续未使用页面部分来执行请求的映射。
您应该阅读Eric Lippert的博客文章“Out of Memory”并不是指物理内存
做下面两件事情你好得多。
- 重用您已分配的内存块(为什么要创建具有完全相同大小的内存块)
- 分配更小的块(少于85KB )
在Dotnet 4.5之前,Dotnet构建了两个堆, 小对象堆(SOH)和大对象堆(LOH) 。 请参阅Brandon Bray 在.NET 4.5中的大对象听力改进 。 您的MemoryStream
正在LOH中分配,并且在进程持续时间内未进行压缩(碎片整理),因此分配大量内存的多次调用将更有可能抛出OutOfMemoryException
CLR管理两个不同的分配堆,小对象堆(SOH)和大对象堆(LOH)。 任何大于或等于85,000字节的分配都在LOH上。 复制大型对象会降低性能,因此与SOH不同,LOH不会被压缩。 另一个定义特征是LOH仅在第2代收集期间收集。 总之,它们具有内置的假设,即大对象分配很少。
看起来你的分配太多,而不是你的系统可以处理的。 你的代码在我的机器上运行正常,但如果我改变它:
const int bufferSize = 1024 * 1024 * 2;
我得到了和你一样的错误。
但是如果我将目标处理器更改为x64,那么代码就会运行,这似乎是合理的,因为你可以解决更多的内存问题。
关于这篇文章的详细解释: http : //www.guylangston.net/blog/Article/MaxMemory关于这个问题的一些信息: .NET进程可以分配的最大内存
首先, Dispose()
不保证将释放内存(它不会标记GC集合的对象,在MemoryStream
情况下 – 它不会释放任何内容,因为MemoryStream
没有非托管资源)。 释放MemoryStream
使用内存的唯一可靠方法是丢失对它的所有引用并等待垃圾收集发生(如果你有OutOfMemoryException
– 垃圾收集器已经尝试但无法释放足够的内存)。 此外,分配这样大的对象(任何> 85000字节)会产生一些后果 – 这些对象将转向大对象堆(LOH),这可能会碎片化(并且无法压缩)。 由于.NET对象必须占用连续的字节序列,因此可能导致您有足够的内存,但没有大对象的空间。 在这种情况下,垃圾收集器无济于事。
这里的主要问题似乎是stream
对象的引用保留在堆栈上,防止stream
对象的垃圾收集(甚至强制垃圾收集也无济于事,因为GC认为对象仍然存活,你可以检查这个创建一个WeakRefrence
to it)。 重构此示例可以修复它:
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); } } }
这是一个certificate对象不能被垃圾收集的示例:
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); } } }
当您确定需要清理未引用的对象时,请尝试强制进行垃圾回收。
GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
另一种方法是使用Stream
with external storage: FileStream
。
但是,在一般情况下,最好使用一个足够小的缓冲区(数组,分配一次)并将其用于读/写调用。 避免在.NET中有许多大对象 (参见CLR Inside Out:Large Object Heap Uncovered )。
更新
假设writesCount
是常量,为什么不分配一个缓冲区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); } } }