关于c#:如果未关闭.NET中的MemoryStream,是否会造成内存泄漏?

Is a memory leak created if a MemoryStream in .NET is not closed?

我有以下代码:

1
2
3
4
5
6
7
8
9
10
11
MemoryStream foo(){
    MemoryStream ms = new MemoryStream();
    // write stuff to ms
    return ms;
}

void bar(){
    MemoryStream ms2 = foo();
    // do stuff with ms2
    return;
}

我分配的MemoryStream是否有可能以后无法以某种方式处置?

我有一个同行审查,坚持要求我手动关闭此链接,但我找不到信息来说明他是否有正确的观点。


您不会泄漏任何东西-至少在当前的实现中。

调用Dispose不会更快地清理MemoryStream占用的内存。呼叫之后,这将使您的流无法进行"读/写"呼叫,这可能对您有用或无效。

如果您绝对确定从不希望从MemoryStream转移到另一种流,则不调用Dispose不会对您造成任何伤害。但是,一般来说,这是一种很好的做法,部分原因是,如果您确实更改了使用其他Stream的方法,那么您就不想被难以发现的bug所困扰,因为您早早选择了简便的方法。 (另一方面,有YAGNI参数...)

无论如何,这样做的另一个原因是,新的实现可能会引入将在Dispose上释放的资源。


如果某些物品是一次性的,则应始终将其丢弃。您应该在bar()方法中使用using语句,以确保ms2被释放。

它最终将被垃圾收集器清除,但是调用Dispose始终是一个好习惯。如果在代码上运行FxCop,则会将其标记为警告。


是的,存在泄漏,具体取决于您定义"泄漏"的方式以及之后的含义...

如果说泄漏是指"即使在使用完毕后,内存仍保持分配状态,无法使用",而后者是指在调用dispose之后的任何时间,那么可能是有泄漏的,尽管它不是永久的(例如应用程序运行时的寿命)。

要释放MemoryStream所使用的托管内存,您需要通过取消对它的引用来取消对其的引用,以便立即有资格进行垃圾回收。如果您未能做到这一点,那么您将在使用完之后创建一个临时泄漏,直到您的引用超出范围为止,因为在此期间内存将无法分配。

using语句(而不是简单地调用dispose)的好处是,您可以在using语句中声明引用。当using语句完成时,不仅会调用dispose,而且您的引用也会超出范围,从而有效地使引用无效,并使您的对象立即有资格进行垃圾回收,而无需您记住编写" reference = null"代码。

尽管不能立即取消引用某些内容并不是经典的"永久"内存泄漏,但绝对具有相同的效果。例如,如果您保留对MemoryStream的引用(即使在调用dispose之后),并且在方法中稍稍下移,则尝试分配更多的内存...仍被引用的内存流正在使用的内存将不可用直到您取消引用或引用超出范围为止,即使您调用了dispose并且已完成使用它。


这已经得到了回答,但是我只是补充说,信息隐藏的良好老式原则意味着您可能会在将来的某个时刻重构:

1
2
3
4
5
6
MemoryStream foo()
{    
    MemoryStream ms = new MemoryStream();    
    // write stuff to ms    
    return ms;
}

至:

1
2
3
4
Stream foo()
{    
   ...
}

这强调了调用者不必关心返回的是哪种类型的Stream,并可以更改内部实现(例如,在进行单元测试的模拟时)。

如果您没有在bar实现中使用Dispose,则可能会遇到麻烦:

1
2
3
4
5
6
7
8
void bar()
{    
    using (Stream s = foo())
    {
        // do stuff with s
        return;
    }
}

不需要调用.Dispose()(或使用using换行)。

调用.Dispose()的原因是要尽快释放资源。

考虑一下,例如Stack Overflow服务器,那里的内存有限,有成千上万的请求进入。我们不想等待调度的垃圾回收,我们希望尽快释放该内存以便可用用于新的传入请求。


所有流都实现IDisposable。在using语句中包装您的Memory流,您会很好并且很花哨。 using块将确保您的流无论如何都被关闭和处置。

无论您在哪里调用Foo,都可以使用(MemoryStream ms = foo())进行操作,我认为您应该还可以。


我建议将MemoryStream包装在using语句的bar()中,主要是为了保持一致性:

  • 目前,MemoryStream不会释放.Dispose()上的内存,但是有可能在将来的某个时候,或者您(或公司中的其他人)可能会用您自己的自定义MemoryStream代替它,等等。
  • 这有助于在您的项目中建立模式,以确保所有流都得到处置-通过说"必须丢弃所有流"而不是"必须丢弃某些流,但不必丢弃某些流"来更明确地划清界限...
  • 如果您更改了代码以允许返回其他类型的Stream,则无论如何都需要对其进行更改。

在创建和返回IDisposable时,在foo()之类的情况下,我通常要做的另一件事是确保构造对象与return之间的任何故障都被异常捕获,处置对象并重新抛出该异常:

1
2
3
4
5
6
7
8
9
10
11
12
MemoryStream x = new MemoryStream();
try
{
    // ... other code goes here ...
    return x;
}
catch
{
    //"other code" failed, dispose the stream before throwing out the Exception
    x.Dispose();
    throw;
}

您不会泄漏内存,但是您的代码审阅者是正确的,它指示您应该关闭流。这样做很有礼貌。

唯一可能导致内存泄漏的情况是,您不小心留下了对流的引用而从不关闭它。您仍然没有真正泄漏内存,但是您不必要地延长了声称使用内存的时间。


如果对象实现IDisposable,则必须在完成后调用.Dispose方法。

在某些对象中,Dispose的含义与Close相同,反之亦然,在这种情况下,两者都不错。

现在,对于您的特定问题,不,您不会泄漏内存。


我不是.net专家,但也许这里的问题是资源,即文件句柄而不是内存。我猜垃圾回收器最终将释放流并关闭句柄,但是我认为最好将其显式关闭以确保将内容刷新到磁盘上。


在垃圾收集语言中,非托管资源的处置是不确定的。即使您显式调用Dispose,也绝对无法控制何时真正释放后备内存。当对象超出范围时,将隐式调用Dispose,无论是通过退出using语句,还是从从属方法弹出调用堆栈。综上所述,有时对象实际上可能是托管资源(例如文件)的包装。这就是为什么最好在finally语句中明确关闭或使用using语句的原因。
干杯


MemorySteram就是字节数组,它是托管对象。
忘记处理或关闭它,除了最终确定的开销之外没有其他副作用。
只需在反射器中检查MemoryStream的构造函数或flush方法,就可以清楚地知道,除了遵循良好实践之外,您无需担心关闭或处置它。