如果 .NET 中的内存流未关闭,则会产生内存泄漏

本文关键字:内存 泄漏 NET 如果 | 更新日期: 2023-09-27 17:47:22

我有以下代码:

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

我分配的内存流是否有可能在以后以某种方式无法处理?

有一个同行评审坚持要我手动关闭它,我找不到信息来判断他是否有有效的观点。

如果 .NET 中的内存流未关闭,则会产生内存泄漏

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

调用Dispose不会更快地清理MemoryStream使用的内存。它将阻止您的流在调用后用于读/写调用,这可能对您有用,也可能没有用。

如果你绝对确定你永远不想从MemoryStream移动到另一种流,那么不调用Dispose不会对你造成任何伤害。但是,这通常是很好的做法,部分原因是如果您确实更改为使用不同的 Stream,您不希望被难以找到的错误咬伤,因为您很早就选择了简单的方法。(另一方面,还有YAGNI的论点...

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

如果某些东西是一次性的,你应该总是处理它。您应该在 bar() 方法中使用 using 语句来确保ms2被释放。

它最终会被垃圾回收器清理,但调用 Dispose 始终是一种很好的做法。如果您在代码上运行 FxCop,它会将其标记为警告。

是的,有泄漏,这取决于你如何定义 LEAK 以及你的意思是多久以后......

如果您所说的泄漏是指"内存仍然已分配,即使您已经使用了它,也无法使用",而后者是指调用 dispose 之后的任何时候,那么是的,可能存在泄漏,尽管它不是永久性的(即在您的应用程序运行时的生命周期内)。

要释放 MemoryStream 使用的托管内存,您需要取消引用它,方法是取消对它的引用,以便它立即符合垃圾回收的条件。 如果您未能执行此操作,则从使用它开始创建临时泄漏,直到引用超出范围,因为在此期间内存将不可用于分配。

using 语句

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

虽然未能立即取消引用某些内容不是经典的"永久"内存泄漏,但它肯定具有相同的效果。 例如,如果您保留对 MemoryStream 的引用(即使在调用 dispose 之后),并且在方法的更下方,您尝试分配更多内存......在使引用无效或引用超出范围之前,仍引用的内存流正在使用的内存将不可用,即使您调用了 Dispose 并完成了使用它。

不需要

调用.Dispose()(或用Using包装)。

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

比如说,想想Stack Overflow服务器,我们有一组有限的内存和数千个请求。我们不想等待计划的垃圾回收,我们希望尽快释放该内存,以便它可用于新的传入请求。

这已经得到了回答,但我只想补充一点,信息隐藏的老式原则意味着你可能在将来的某个时候想要重构:

MemoryStream foo()
{    
    MemoryStream ms = new MemoryStream();    
    // write stuff to ms    
    return ms;
}

自:

Stream foo()
{    
   ...
}

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

然后,如果您没有在 bar 实现中使用 Dispose 处理器,您将需要遇到麻烦:

void bar()
{    
    using (Stream s = foo())
    {
        // do stuff with s
        return;
    }
}

所有流都实现 IDisposable。 将您的内存流包装在 using 语句中,您将变得很好且花花公子。 使用块将确保您的流无论如何都关闭和处置。

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

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

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

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

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 与 Close 的含义相同,反之亦然,在这种情况下,两者都是好的。

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

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

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

MemorySteram 只不过是字节数组,它是托管对象。忘记处理或关闭它除了完成之外没有任何副作用。
只需在反射器中检查MemoryStream的结构或刷新方法,就会清楚为什么您不需要担心关闭或处置它,而不仅仅是遵循良好做法。